I have an image:
newGameButton = pygame.image.load("images/newGameButton.png").convert_alpha()
I then display it on the screen:
screen.blit(newGameButton, (0,0))
How do I detect if the mouse is touching the image?
Use Surface.get_rect to get a Rect describing the bounds of your Surface, then use .collidepoint() to check if the mouse cursor is inside this Rect.
Example:
if newGameButton.get_rect().collidepoint(pygame.mouse.get_pos()):
print "mouse is over 'newGameButton'"
I'm sure there are more pythonic ways to do this, but here is a simple example:
button_x = 0
button_y = 0
newGameButton = pygame.image.load("images/newGameButton.png").convert_alpha()
x_len = newGameButton.get_width()
y_len = newGameButton.get_height()
mos_x, mos_y = pygame.mouse.get_pos()
if mos_x>button_x and (mos_x<button_x+x_len):
x_inside = True
else: x_inside = False
if mos_y>button_y and (mos_y<button_y+y_len):
y_inside = True
else: y_inside = False
if x_inside and y_inside:
#Mouse is hovering over button
screen.blit(newGameButton, (button_x,button_y))
Read more on the mouse in pygame, as well as surfaces in pygame.
Also here is an example closely related to this.
Related
I should point out that I'm a beginner with PyGame. I have made a program that displays some simple graphics on the screen using PyGame. It blits every graphic on a dummy surface and the dummy surface gets scaled and blit to a 'real' surface that gets displayed on the screen in the end. This allows the program to have a resizable window without messing the graphics and UI.
I have also made my own 'Button' class that allows me to draw clickable buttons on the screen. Here it is:
import pygame
pygame.font.init()
dfont = pygame.font.Font('font/mfdfont.ttf', 64)
#button class button(x, y, image, scale, rot, text_in, color, xoff, yoff)
class Button():
def __init__(self, x, y, image, scale = 1, rot = 0, text_in = '', color = 'WHITE', xoff = 0, yoff = 0):
self.xoff = xoff
self.yof = yoff
self.x = x
self.y = y
self.scale = scale
width = image.get_width()
height = image.get_height()
self.image = pygame.transform.rotozoom(image, rot, scale)
self.text_in = text_in
self.text = dfont.render(self.text_in, True, color)
self.text_rect = self.text.get_rect(center=(self.x +width/(2/scale) + xoff, self.y + height/(2/scale) + yoff))
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
self.clicked = False
def draw(self, surface):
action = False
#get mouse position
pos = pygame.mouse.get_pos()
#check mouseover and clicked conditions
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
self.clicked = True
action = True
if pygame.mouse.get_pressed()[0] == 0:
self.clicked = False
#draw button on screen
surface.blit(self.image, (self.rect.x, self.rect.y))
surface.blit(self.text, self.text_rect)
return action
When I need to draw one of these buttons on the screen I firstly define it like this:
uparrow = button.Button(128, 1128, arrow_img, 0.5, 0, "SLEW", WHITE, 0, 128)
Then I call it's draw function like this:
if uparrow.draw(screen):
print('UP')
It works reasonably well when drawing it to a surface that doesn't get scaled. This is the problem. When I scale the dummy surface that it gets drawn to, the button's image and text scale just fine but it's collider does not. So when I click on it nothing happens, but if I click on the location of the screen the button would have been on the unscaled dummy surface it works.
Just for context, the dummy surface is 2048x1024 and the 'real' surface is much smaller, starting at 1024x512 and going up and down however the user resizes the window. The game maintains a 2:1 aspect ratio though, so any excess pixels in the game window are black. You can see this in the screenshot below:
Above is a screenshot of the game window. You can see the 'NORM' button at the top of the game screen, and the red box that roughly represents the same 'NORM' button's actual collider. It's basically where it would be on the dummy surface.
(I have previously posted a question on somewhat the same problem as this one, but at that time I didn't know the colliders actually worked and I thought my clicks just didn't register on the buttons, which is not the case).
I'd like to know what part of my button class causes this and how it should be refactored to fix this issue. Alternatively, if you think it's caused by my double surface rendering technique or anything else really, please do point me in the right direction.
In your setup you draw the buttons on an surface, scale the surface and blit that surface on the display. So you do something like the following:
dummy_surface = pygame.Surface((dummy_width, dummy_height)
while True:
# [...]
scaled_surface = pygame.transform.scale(dummy_surface, (scaled_width, scaled_height))
screen.blit(scaled_surface, (offset_x, offset_y))
For click detection to work on the original buttons, you must scale the position of the mouse pointer by the reciprocal scale and shift the mouse position by the inverse offset:
def draw(self, surface):
action = False
# get mouse position
pos = pygame.mouse.get_pos()
scale_x = scaled_width / dummy_surface.get_width()
scale_y = scaled_height / dummy_surface.get_height()
mx = int((pos[0] - offset_x) / scale_x)
my = int((pos[1] - offset_y) / scale_y)
pos = (mx, my)
# [...]
This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Pygame mouse clicking detection
(4 answers)
How to use Pygame touch events in a mobile game?
(1 answer)
Closed 7 days ago.
I'm making a PyGame game and I have made a Button class to make on-screen buttons. This is the code:
import pygame
pygame.font.init()
dfont = pygame.font.Font('mfdfont.ttf', 64)
#button
class Button():
def __init__(self, x, y, image, scale = 1, rot = 0, text_in = '', color = 'WHITE', xoff = 0, yoff = 0):
self.xoff = xoff
self.yof = yoff
self.x = x
self.y = y
self.scale = scale
width = image.get_width()
height = image.get_height()
self.image = pygame.transform.rotozoom(image, rot, scale)
self.text_in = text_in
self.text = dfont.render(self.text_in, True, color)
self.text_rect = self.text.get_rect(center=(self.x +width/(2/scale) + xoff, self.y + height/(2/scale) + yoff))
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
self.clicked = False
def draw(self, surface):
action = False
#get mouse position
pos = pygame.mouse.get_pos()
#check mouseover and clicked conditions
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
self.clicked = True
action = True
if pygame.mouse.get_pressed()[0] == 0:
self.clicked = False
#draw button on screen
surface.blit(self.image, (self.rect.x, self.rect.y))
surface.blit(self.text, self.text_rect)
return action
The problem is that I'm using two surfaces to draw the game to the screen. One is a dummy surface at 1024x2048 resolution that I draw everything to, and the second one is a surface that I can resize to any resolution. The dummy surface then gets scaled and blit to the real surface and the real surface is drawn on the screen. This allows me to have a resizable window without messing the screen positions of UI and game elements.
The actual problem is that after implementing this second surface, click and touch input on buttons doesn't work anymore because I'm basically clicking on the real surface and not the dummy surface the buttons are drawn on. I wonder if there is a way to redirect clicks from a certain position on the real surface to clicks to the relative position on the dummy surface. Or maybe have the button class listen for input on the real surface instead of the dummy surface it's drawn on.
I added a "mobile mode", so when the game is running on a mobile device the entire two surface rendering process is not used and instead uses the classic one surface rendering, which makes the buttons work on mobile devices (or devices that don't allow window resizing). This is a temporary fix for the mobile version, but that still leaves the desktop application unusable because the on screen buttons don't work. I must mention that the OSBs are needed, I won't add keyboard controls for those buttons instead. They must be on screen.
I am new to python and wanted to create a game to test some knowledge. Please bare with my mistakes. I would also really appreciate an example written out if possible so I can wrap my head around the concept.
I couldn't figure out how to post the entire code without formatting nightmares so I put it in github sorry if this is against terms!
I removed most of the dialogue because it was very very long.
Some background about the same:
It was written as a text-based only game first, then I heard about pygame wanted to figure out how to put the game into the window. So first I created all of the code, which ran perfectly fine even with sound effects / music, but then after trying to figure out pygame's game loop, I can't figure out how to change the background image.
I have 5 scenes that I want 5 images for, that change when I change the scene with user input. Even now, the music changes, but the background images don't change after blitting the first one in the 'naming' scene. I don't really understand how the pygame loop works in conjunction with the game engine I have currently. I know it's looping through the pygame loop as I tested it with printing things, but when trying to add images or saying if scene == naming: blit image, it doesn't work. I also tried putting the images into each of the scenes. I also tried putting the images into a class called background with all 5 scenes and images to each. Can't figure it out.
Any help would be greatly appreciated!
-M
Here is an example of how to use scenes. This is not the only way of doing it, but i though it would be easy to use/understand. I also noticed in your code that you used input(). which is annoying if your using a window, so i also added a button and a textbox input that kinda works.
import pygame as pg
pg.init() # initialize the pg
win = pg.display.set_mode((1080, 720)) # create the game window
pg.display.set_caption("Puppy Love")
white = (255,255,255)
black = (0,0,0)
clock = pg.time.Clock()
fps = 60
class Scene:
def __init__(self):
self.sceneNum = 1
def Scene_1(self):
win.fill(white)
if next_scene_button.draw(): #if clicked button
self.sceneNum += 1
name_Box.Draw()
#anything else you want to draw
def Scene_2(self):
win.fill((0,0,255))
if next_scene_button.draw():
self.sceneNum = 1
#...
def Draw_Scene(self):
if self.sceneNum == 1:
self.Scene_1()
elif self.sceneNum == 2:
self.Scene_2()
class Button:
def __init__(self,x,y,w,h,text):
self.x = x
self.y = y
self.w = w
self.h = h
self.text = text
self.font = pg.font.Font(pg.font.match_font("Calibri"),20)
def draw(self):
button_clicked = False
col = (150,150,150)
if mouse_pos[0] > self.x and mouse_pos[0] < self.x + self.w:
if mouse_pos[1] > self.y and mouse_pos[1] < self.y + self.h:
col = (200,200,200)
if click:
button_clicked = True
pg.draw.rect(win,col,(self.x,self.y,self.w,self.h))
obj = self.font.render(self.text,True, black)
win.blit(obj,(self.x + (self.w-obj.get_width())//2,self.y + (self.h-obj.get_height())//2)) #center the text
return button_clicked #return if the button was clicked or not
class TextBox:
def __init__(self,x = 0, y = 0, w = 0, h = 0, text = "", background = False, font_size = 30, font = "Calibri", text_colour = (0,0,0)):
self.x = x
self.y = y
self.w = w
self.h = h
self.text_colour = text_colour
self.text = text
self.background = background
self.font = pg.font.Font(pg.font.match_font(font),font_size)
def Add_char(self,key):
if key == 8:
self.text = self.text[:-1]
else:
char = pg.key.name(key)
self.text += char
def Draw(self):
if self.background:
pg.draw.rect(win, self.background, (self.x,self.y,self.w,self.h))
obj = self.font.render(self.text,True,self.text_colour)
win.blit(obj,(self.x,self.y))
def Get_pos(self):
return (self.x,self.y)
name_Box = TextBox(x=100, y=200, w=150, h=40, background=(200,200,200))
Scenes = Scene()
next_scene_button = Button(400,400,100,50,"next scene")
click = False
mouse_pos = [0,0]
running = True
while running:
clock.tick(fps)
Scenes.Draw_Scene()
mouse_pos = pg.mouse.get_pos()
click = False
pg.display.update() #no diference between .flip() and .update()
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
running = False
if event.type == pg.MOUSEBUTTONDOWN:
click = True
if event.type == pg.KEYDOWN:
if Scenes.sceneNum == 1:
name_Box.Add_char(event.key)
The main thing to take away from this is to only use one pygame.display.flip(). Hopefully you can use this to improve your program
I have the following code:
drawing = False # true if mouse is pressed
mode = True # if True, draw rectangle. Press 'm' to toggle to curve
ix,iy = -1,-1
# mouse callback function
def draw_circle(event,x,y,flags,param):
global ix,iy,drawing,mode
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix,iy = x,y
elif event == cv2.EVENT_MOUSEMOVE:
if drawing == True:
if mode == True:
cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
else:
cv2.circle(img,(x,y),20,(0,0,255),-1)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
if mode == True:
cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
else:
cv2.circle(img,(x,y),20,(0,0,255),-1)
I don't understand the use of xi and yi on the rectangle. There is a line which stated that xi,yi = x,y. Would it be the same? How would it draw rectangle? From what I understand, to draw a rectangle, there should be two sets of coordinates. I can't see which part in the line that shows that ix,iy will be different with x and y value.
Can anyone explain?
Its pretty straightforward here is what happens in steps:
the user clicks down - current mouse position x,y is stored as ix,iy and drawing is set to true
the user moves the mouse - if drawing is true and mode is true draws a rectangle using the saved ix,iy and the new current x,y (note that ix,iy does NOT equal x,y anymore since its only saved when the mouse button down triggers)
the user releases the mouse - drawing is set to false so the mouse can be moved without drawing anything and any current images are saved
I am working on this game in pygame and I need to blit some letters on the screen one by one so I created the following code:
def suspicious_print(text, font, font_big, t_w, t_h, fill_colour, rect_colour, text_colour):
pygame.font.init()
font = pygame.font.SysFont(font, font_big)
window_disp.fill(fill_colour)
text_print = ""
for letter in text:
text_print += letter
render = font.render(text_print, 1, text_colour)
render_rect = render.get_rect(center = (t_w, t_h))
pygame.draw.rect(window_disp, rect_colour, render_rect)
window_disp.blit(render, render_rect)
pygame.display.update()
pygame.time.delay(500)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.font.quit()
pygame.quit()
quit()
The problem is the words are moving to the left side every time the letter appears. How can I prevent the words from moving around and have them stay still?
The text is getting centered around (t_w, t_h) because of render_rect = render.get_rect(center = (t_w, t_h)). Instead of centering you need to left justify the rectangle and always draw it in the same place.
render_rect = render.get_rect()
render_rect.x = t_w
render_rect.y = t_h
If you want the whole message to be rendered in the center of the screen then you'll need to know 2 things: the size of the final rendered text and the location of the center of the screen. The size of the final box can be found by using the pygame.font.Font.size method on your font. In your case, the following lines should work:
font = pygame.font.SysFont(font, font_big)
box_width, box_height = font.size(text)
Then you can set the position of render_rect:
render_rect.x = screen_center_x - box_width / 2
render_rect.y = screen_center_y - box_height / 2
I'll leave it up to you to get the center of the screen.