Yes, that title wasn't worded very properly at all.
Ok, here's what we've got - a Python program using the pyGame library, and we're making a game. We start in a menu environment main.py. When the user clicks on one of the menu buttons, an action is performed. The program checks for clicks on menu items using the following code:
if event.type == pygame.MOUSEBUTTONDOWN:
mousePos = pygame.mouse.get_pos()
for item in buttons: # For each button
X = item.getXPos() # Check if the mouse click was...
Y = item.getYPos() # ...inside the button
if X[0] < mousePos[0] < X[1] and Y[0] < mousePos[1] < Y [1]:
# If it was
item.action(screen) # Do something
When the user clicks on the "Play Game" button, it opens a sub-module, playGame.py. In this sub-module is another pyGame loop etc.
Part of the game is to hold the left mouse button to 'grow' circles out of the current position (It's a puzzle game, and it makes sense in context). Here is my code for doing this:
mouseIsDown == False
r = 10
circleCentre = (0,0)
[...other code...]
if mouseIsDown == True:
# This grown the circle's radius by 1 each frame, and redraws the circle
pygame.draw.circle(screen, setColour(currentColourID), circleCentre, r, 2)
r += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
runningLevel = False
elif event.type == pygame.MOUSEBUTTONDOWN:
# User has pressed mouse button, wants to draw new circle
circleCentre = pygame.mouse.get_pos()
mouseIsDown = True
elif event.type == pygame.MOUSEBUTTONUP:
# Stop drawing the circle and store it in the circles list
mouseIsDown = False
newCircle = Circle(circleCentre, r, currentColourID)
circles.append(newCircle)
circleCount += 1
r = 10 # Reset radius
The problem I have is that the user's left mouse click from the main menu is persisting into the playGame.py module, and causing it to create and store a new circle, of radius 10 and at position (0,0). Both of these are the default values.
This only happens for the one frame after the menu.
Is there any way to prevent this, or is it a flaw in my code?
All help greatly appreciated, as always. If you need more code or explanation of these snippets, let me know.
If you'd like the full code, it's on GitHub.
You could use MOUSEBUTTONUP instead of MOUSBUTTONDOWN in the menu.
Does adding pygame.event.clear() to the top of Play fix it?
Related
I'm attempting to create a pygame Tic-Tac-Toe game. My issue is in my 'mousemovement()' method. Whenever i use the 'pygame.mouse.getpos()' in my 'collidepoint()' method it gives me a type error and says that my tuples index is out of range. Anyone have any ideas?
import pygame
pygame.init()
pygame.font.init()
pygame.display.set_caption("Tic-Tac-Toe")
pygame.mouse.set_visible(True)
FPS = 60
clock = pygame.time.Clock()
WIDTH,HEIGHT = 500,600
BORDER_THICKNESS = 10
WIN = pygame.display.set_mode((WIDTH,HEIGHT))
#delcare the fonts of the characters
GAME_FONT = pygame.font.SysFont("comicsans",100)
#colors
WHITE = (255,255,255)
BLACK = (0,0,0)
GREEN = (0,255,0)
def display_window(squares,marks,turn):
WIN.fill(WHITE)
starting_pos = WIDTH // 3
for l in range(2): #draws the board
vert = pygame.Rect((starting_pos - BORDER_THICKNESS//2,0),(BORDER_THICKNESS,HEIGHT))
hrzn = pygame.Rect((0,starting_pos - BORDER_THICKNESS//2),(WIDTH,BORDER_THICKNESS))
starting_pos = starting_pos * 2 + BORDER_THICKNESS//2
pygame.draw.rect(WIN,BLACK,vert)
pygame.draw.rect(WIN,BLACK,hrzn)
#draws black background for text box
more_border = pygame.Rect((0,WIDTH),(WIDTH,HEIGHT - WIDTH))
pygame.draw.rect(WIN,BLACK,more_border)
#draws actual text box
text_box = pygame.Rect((0+BORDER_THICKNESS,WIDTH+BORDER_THICKNESS),(WIDTH-BORDER_THICKNESS*2,HEIGHT - WIDTH))
pygame.draw.rect(WIN,WHITE,text_box)
#display game squares
for s in squares:
pygame.draw.rect(WIN,GREEN,s)
#prints the marks of x's and o's
for m in marks:
pass #still working on printing the marks
pygame.display.update()
#use the mouse methods in order to retrive location in which
#mouse is at and see if it collides with a given square
def mouse_movement(squares,mouse_presses,marks,turn):
for s in squares:
if s.collidepoint(pygame.mouse.get_pos()) and mouse_presses[pygame.MOUSEBUTTONDOWN]:
if turn % 2 == 1:
marks[squares.index(s)] = 'X'
elif turn % 2 == 0:
marks[squares.index(s)] = 'O'
else:
raise TypeError("Neither condition is being met")
def main():
turn = 1
x, y = 0,0
squares = []
for c in range(3):
for r in range(3):
squares.append(pygame.Rect((x,y),(WIDTH//3-BORDER_THICKNESS//2,WIDTH//3-BORDER_THICKNESS//2)))
x += WIDTH//3 - BORDER_THICKNESS//2 + BORDER_THICKNESS
x = 0
y += WIDTH//3 - BORDER_THICKNESS//2 + BORDER_THICKNESS
marks = ['','','','','','','','','']
game_going = True
while game_going:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_going = False
pygame.quit()
print(pygame.mouse.get_pos())
mouse_presses = pygame.mouse.get_pressed()
marks = mouse_movement(squares,mouse_presses,marks,turn)
display_window(squares,marks,turn)
if __name__ == '__main__':
main()
I searched the pygame website to make sure that the 'pygame.mouse.get_pos()' method returns a tuple and it does. I'm not quite sure where to go from here.
You get the out of range exception because of mouse_presses[pygame.MOUSEBUTTONDOWN].
pygame.mouse.get_pressed() returns a list of Boolean values that represent the state (True or False) of all mouse buttons. The state of a button is True as long as a button is held down. When multiple buttons are pressed, multiple items in the list are True. The 1st, 2nd and 3rd elements in the list represent the left, middle and right mouse buttons.
If you want to test if the left mouse button is pressed it is:
if mouse_presses[0]:
If you want to test if any button is pressed it is:
if any(mouse_presses):
However, this is not how the MOUSEBUTTONDOWN event works. The MOUSEBUTTONDOWN event occurs once when you click the mouse button and the MOUSEBUTTONUP event occurs once when the mouse button is released. The pygame.event.Event() object has two attributes that provide information about the mouse event. pos is a tuple that stores the position that was clicked. button stores the button that was clicked. Each mouse button is associated a value. For instance the value of the attributes is 1, 2, 3, 4, 5 for the left mouse button, middle mouse button, right mouse button, mouse wheel up respectively mouse wheel down. When multiple keys are pressed, multiple mouse button events occur. Further explanations can be found in the documentation of the module pygame.event.
e.g.:
game_going = True
while game_going:
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_going = False
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # left button
print(event.pos) # mouse position
# [...]
I am making a simple tic tac toe game in which there is an image that I blit on a mouse up event...but due to the game loop, it disappears the very next moment. Here is the code...can anybody help me on how to keep it on-screen once it is blitted by the mouse..
toggle = True
# Game loop.
while True:
box = 0
...
if event.type == pygame.MOUSEBUTTONUP:
pos = pygame.mouse.get_pos()
if rect_1.collidepoint(pos):
box = 1
toggle = not toggle
...
screen.fill((235, 235, 235))
if box != 0:
if (toggle):
if (box == 1):
cross_rect = cross_minified.get_rect(center=rect_1.center)
screen.blit(cross_minified, cross_rect)
can anyone please suggest me a solution please?
Initailize box above the loop:
toggle = True
# Game loop.
box = 0
while True:
...
I Got the Solution Guys, I simply used a list and check if the value is appended or not and if appended I just blitted it to the screen.
Okay, I'am trying to create a Tom and Jerry game with the pygame library.
The game focuses on catching mice by clicking on them as they appear in their holes. The problem
is that sometimes a cat appears instead of a mouse and should the player erroneously click on the
cat (s)he looses all earned points, but the game continues.
The mouse is an image of a mouse and the cat is an image of an cat.
If you click on the mouse, you get mouse, otherwise the cat gets the points.
The code is a mess, that's because I don't know what I'am doing and just set an another event loop because then it works, because it runs after I create the mouse. It works to click on the mouse but then you click somewhere else and after that it's like you did not clicked on the mouse.
The mouse is created in a loop and is supposed to wait for 5 seconds and if you click on the mouse within these seconds then an appropriate message prints out in the console ,,Jerry clicked!" else "1 click". If you don't click on the mouse within 5 seconds a image covers the mouse so she disappears.
Now, what I'am trying to do right now is to print the message 1 click when the player does not click on anything but print 1 click jerry clicked when the player clicks on the mouse. I have a image of the mousehole and then I put the mouse on the mousehole, that is, on an another image.
This code works with one image at least:
pygame.init()
width=350;
height=400
screen = pygame.display.set_mode( (width, height ) )
pygame.display.set_caption('clicked on image')
redSquare = pygame.image.load("images/red-square.png").convert()
x = 20; # x coordnate of image
y = 30; # y coordinate of image
screen.blit(redSquare , ( x,y)) # paint to screen
pygame.display.flip() # paint screen one time
running = True
while (running):
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
# Set the x, y postions of the mouse click
x, y = event.pos
if redSquare.get_rect().collidepoint(x, y):
print('clicked on image')
#loop over, quite pygame
pygame.quit()
My problem is that, when I click on the mouse and then I don't click on the mouse I can't click on the mouse again at another position.
So what's wrong? What I'am doing wrong here?
Here is my code:
import pygame
from pygame import *
from random import *
init()
run = True
screen = (800,800)
screen = display.set_mode(screen)
xpos = 0
ypos = 0
mouseorcatxpos = 5
mouseorcatypos = 0
mousehole = image.load("mousehole.png").convert()
cat = image.load("tom.png")
jerry = image.load("jerry.png")
def makeholes():
global ypos
global xpos
for holey in range(1,9):
for holex in range(1,9):
screen.blit(mousehole,(xpos,ypos))
display.flip()
xpos += 100
ypos += 100
xpos = 0
def mouseorcat():
global xpos
mouseorcatxpos = 5
ypos = 0
for mousecaty in range(1,9):
pygame.event.pump()
for mousecatx in range(1,9):
randommouse = randint(1, 3)
randomcat = randint(1, 10)
if(randommouse == 2):
screen.blit(jerry, (mouseorcatxpos, ypos))
display.flip()
for event in pygame.event.get():
if (event.type == MOUSEBUTTONDOWN):
if jerry.get_rect().collidepoint(xpos, ypos) == False:
print("l clicked!")
x, y = event.pos
if jerry.get_rect().collidepoint(xpos, y):
print("JERRY CLICKED!!")
x, y = event.pos
print(x, y)
time.wait(5000)
#screen.blit(mousehole, (mouseorcatxpos - 5, ypos))
display.flip()
elif(randomcat == 2):
screen.blit(cat, (mouseorcatxpos, ypos))
display.flip()
time.wait(1500)
screen.blit(mousehole, (mouseorcatxpos-5, ypos))
display.flip()
mouseorcatxpos += 100
mouseorcatxpos = 0
ypos += 100
makeholes()
while run == True:
for event in pygame.event.get():
mouseorcat()
if event.type == QUIT:
run = False
I rewrote your game to show you how I would do it.
To keep track of the time and to limit the framerate I used a pygame.time.Clock and a timer variable. The clock returns the time in milliseconds since clock.tick was called the last time, which is used to increase the timer variable. The cat just replaces the mouse after two seconds and the mouse is set to a new position. I use pygame.Rects to store the positions, but you could also use lists or tuples.
import sys
import random
import pygame
pygame.init()
size = (800, 800)
screen = pygame.display.set_mode(size)
# Images replaced by pygame.Surface. Do that too
# in the future before you post your code.
mousehole = pygame.Surface((40, 40)).convert()
mousehole.fill(pygame.Color(30, 30, 30))
cat = pygame.Surface((40, 40)).convert()
cat.fill(pygame.Color(110, 110, 130))
jerry = pygame.Surface((40, 40)).convert()
jerry.fill(pygame.Color(190, 130, 0))
# Create the background image and blit the holes.
background = pygame.Surface(size).convert()
for holey in range(8):
for holex in range(8):
background.blit(mousehole, (holex*100, holey*100))
def new_position():
"""Return a random position between 0-700 in steps of 100."""
return (random.randrange(0, 701, 100), random.randrange(0, 701, 100))
def main():
fps = 30
clock = pygame.time.Clock()
jerry_rect = jerry.get_rect() # Stores jerry's position and size.
jerry_rect.topleft = new_position() # New random position.
# The cat is outside of the screen first.
cat_rect = cat.get_rect(topleft=(-100, -100))
points = 0
timer = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if jerry_rect.collidepoint(event.pos):
points += 1
print('Jerry caught! Points:', points)
timer = 0
jerry_rect.topleft = new_position()
else:
print('Missed. Points:', points)
# Run logic.
timer += clock.tick(fps) / 1000 # timer + seconds since last tick.
if timer > 2: # Cat catches mouse after 2 seconds.
cat_rect.topleft = jerry_rect.topleft
jerry_rect.topleft = new_position()
timer = 0
points = 0
print('Tom caught Jerry.')
# Draw.
# Clear the screen by blitting the bg.
screen.blit(background, (0, 0))
screen.blit(jerry, jerry_rect)
screen.blit(cat, cat_rect)
pygame.display.flip()
if __name__ == '__main__':
main()
pygame.quit()
sys.exit()
Side notes:
Don't use star imports (from module import *), because that can make code harder to read. If you want you can use from pygame.locals import *, if it's the only star import.
Don't use global variables, because they can make code harder to read, understand and maintain. Pass variables to functions as arguments and then return the result.
Update: Some notes about your program:
The first big problem is that your game has two event loops and the important one is deeply nested inside of two other for loops and a if. The event loop should be directly under the main while loop (one indentation level (when you have more experience you can put it into a function or class method)).
The two for loops seem to have the purpose to let the code run until randommouse or randomcat are 2. To run code until a condition is met is the purpose of a while loop. But in this case you should better just pick a random number and write the if/elif conditions so that they always apply. For example, you want a 2/3 chance for mouse and 1/3 for a cat,
random_number = random.randint(1, 3)
if random_number < 3:
print("2/3 probability. It's a mouse")
else:
print("1/3 probability. It's a cat")
Or use random.choice with a list:
>>> random.choice(['mouse', 'mouse', 'cat'])
'mouse'
time.wait(5000) shouldn't be used because the game just hangs in this time. You can't even close the window. Limit the framerate and get the time since the last tick with a pygame.time.Clock.
pygame.event.pump() is not needed.
If you call get_rect() without an argument, the rect is positioned at (0, 0).
if jerry.get_rect().collidepoint(xpos, y):
That's the reason why clicking on jerry only works in the top row, and because you use the global xpos here. Since xpos is 0, the whole top row counts as Jerry.
You can pass coordinates to get_rect like so (you can also use center or other args instead of topleft):
jerry_rect = jerry.get_rect(topleft=(50, 100))
I'm sorry but I don't think I can simply fix your code. I've tried it several times, but I always end up re-writing it completely.
I begin by extracting the event loop out of the two nested for loops, then remove these loops, create rects for the mouse and cat, fix the collision detection, add a timer and so on. Take a close look at my example and try to rewrite your game in a similar way, and keep asking questions if you don't understand something.
I'm having an issue with pygame. I've set up a window that randomly places circles across the screen very quickly, just for testing purposes. There are also three buttons: play/pause (switches back and forth, stops circles from appearing) and an increase speed and decrease speed button. I'm not very experienced with python or pygame, but I've come up with this function to create a clickable button on the screen:
def makeButton(rect, color, hovercolor, text, textsize, textcolor):
clicked = False
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
clicked = True
mouse = pygame.mouse.get_pos()
rect = pygame.Rect(rect)
displaycolor = color
if rect.collidepoint(mouse):
displaycolor = hovercolor
buttonSurface = pygame.draw.rect(gameDisplay, displaycolor, rect, 0)
font = pygame.font.Font('freesansbold.ttf',textsize)
TextSurf = font.render(text, True, textcolor)
TextRect = TextSurf.get_rect()
TextRect.center = rect.center
gameDisplay.blit(TextSurf, TextRect)
if clicked:
return True
else:
return False
This function can definitely be shortened and simplified, but it has worked for me, up until now. I took out a big chunk of code that I realized was useless (having a completely different block of code to render the button when hovered, instead of just changing the display color). Now, whenever I click any of the three previously-mentioned buttons, it seems to pick a random one and return True, messing up the rest of the program. For example, the play button will increase the speed one time, pressing decrease speed will pause, etc. Sometimes it does do what it is supposed to, but it seems to be random.
Some extra info, if it's useful:
-This function is called three times every tick. It's inside a loop, and if it returns true, its corresponding actions are supposed to be performed (pause or play the game, increase/decrease speed)
-The play/pause button is one button that toggles between green with an 'play' arrow, and red with a pause symbol. They are two separate buttons and functions, and only one of them is executed at a time.
-I have almost zero experience with classes, so they may be way better at handling this situation.
-The only explanation I can think of for this problem is that the returned booleans are getting mixed up between the different places this function is used. I'm pretty sure the problem is within this chunk of code, but ask me and I will post the places it is called too.
"pygame.event.get()" takes one event at a time, and *clears it** from the list of events that need to be processed.
So, more specifically, pygame.event.get() returns each event only once.
Take a look at the following code:
clicked = False
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
clicked = True
After this is called, all of the events are removed. Here is an analysis of the code. Assume that there are currently two events that haven't been processed, the first being a key pressed down and the other being a mouse button that's been pressed down.
The first event, event.KEYDOWN, is put into the variable "event".
The program checks whether "event" (currently equal to event.KEYDOWN) is equal to event.MOUSEBUTTONDOWN. They are obviously not the same thing, so the next line is skipped.
The second event, event.MOUSEBUTTONDOWN, is put into variable "event". This removes what was previously in the variable "event", removing the first event from existence.
The program checks whether "event" (currently equal to event.MOUSEBUTTONDOWN) is equal to event.MOUSEBUTTONDOWN. It is, so it proceeds to the next line...
"clicked" is set to True, and the for loop exits, because there are no event remaining.
You should now have a better understanding of how Pygame processes events.
There are also many problems with the function you gave (makeButton). You should find a python tutorial to learn the rest. I suggest a book called "Hello World", by Carter and Warren Sande. The book is kind of out of date (teaches Python 2.5), but its code still works with Python 2.7, and it is one of the few decent Python books I've been able to find.
I have included the code to do what you are trying to do. I don't use Rect objects, but if you want them you can change the code to include them. I also didn't include the text, because I am short on time. Instead of placing random circles, this prints text (to the shell) when buttons are clicked.
import pygame, sys
pygame.init()
screen = pygame.display.set_mode([640,480])
clock = pygame.time.Clock()
buttons = []
#buttons = [[rect, color, hovercolor, hovering, clicked, msg]]
def makeButton(rect, color, hovercolor, text):
global buttons
buttons.append([rect, color, hovercolor, False, False, text])
makeButton([0,0,50,50], [0,127,0], [0,255,0], "Clicked Green")
makeButton([50,0,50,50], [190,190,0], [255,255,0], "Clicked Yellow")
makeButton([100,0,50,50], [0,0,127], [0,0,255], "Clicked Blue")
while 1:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
mousepos = event.pos
for a in range(len(buttons)):
if mousepos[0] >= buttons[a][0][0] and mousepos[0] <= buttons[a][0][0]+buttons[a][0][2] and mousepos[1] >= buttons[a][0][1] and mousepos[1] <= buttons[a][0][1]+buttons[a][0][3]:
buttons[3] = True
else:
buttons[3] = False
if event.type == pygame.MOUSEBUTTONDOWN:
mousepos = event.pos
for a in range(len(buttons)):
if mousepos[0] >= buttons[a][0][0] and mousepos[0] <= buttons[a][0][0]+buttons[a][0][2] and mousepos[1] >= buttons[a][0][1] and mousepos[1] <= buttons[a][0][1]+buttons[a][0][3]:
buttons[4] = True
else:
buttons[4] = False
for a in range(len(buttons)):
if buttons[3] == 0:
pygame.draw.rect(screen, buttons[1], buttons[0])
else:
pygame.draw.rect(screen, buttons[2], buttons[0])
if buttons[4] == 1:
buttons[4] = 0
print buttons[5]
pygame.display.flip()
I haven't had the opportunity to test out the code I just typed (using school computer), but it should work. If there are any problems with the code, just leave a comment and I'll fix it.
Also leave a comment if you don't understand something. Don't give up, you can do it!
Over the summer I decided to make a version of pong using the pygame module in Python. I already have the game logic and now I am just working on a title screen for some added touch. My main goal for this specific function is to be able to select an option on the screen and have it become highlighted orange. However, I keep getting this weird error error every time I try to scroll down via downward button the program ends and displays this error:
error: display Surface quit
From what I've read this comes from declaring things after pygame.quit() has been initiated so I took it out of my code temporarily, yet the same thing happened without it. WTF?
My code (It has a few unused variable due to the fact that I just copied and pasted it to another python doc):
import pygame,random, time
pygame.init()
#GLOBAL VARIABLES
mode='normal'#Sets game theme
playerx=0 #Paddle x for gamer
playery=0 #paddle y for gamer
aix=0 #paddle x for computer
aiy=0 #paddle y form computer
x=400 #ball x coord
y=300 #ball y coord
speed_ballx=0 #xball pixel change during post blit
speed_bally=0 #yball pixel change during post blit
r=0 #redval
g=0 #greval
b=0 #blueval
#GRAPHIC ART
paddle=pygame.image.load('paddle.PNG')
ball=pygame.image.load('ball.PNG')
logo=pygame.image.load('logo.PNG')
comicsans = pygame.font.SysFont("comicsansms", 22) #comicsans font initialization
startoption = comicsans.render("START", True, (255,255,255))
instructionoption = comicsans.render("HELP", True, (255,255,255))
select=pygame.image.load('optionselect.PNG')
def pong():
main_surface.blit(ball, (x,y))#prints ball
def intsructions():
pygame.quit()
def menu():
end=False #will end while loop and end menu
opt=1 #used to decide what option the player is selecting
main_surface = pygame.display.set_mode((815,615))#creates new window
while end==False:
main_surface.blit(logo, (15,15))
pygame.display.flip()#renders all blitted objects
#The following if statements blit orange highlight on to the selected option
if opt==1:
main_surface.blit(select,(12,140))
if opt==2:
main_surface.blit(select,(12,170))
main_surface.blit(startoption, (15,140))
main_surface.blit(instructionoption, (15,170))
pygame.display.flip()#renders all blitted objects
for ev in pygame.event.get():
if ev.type == pygame.QUIT: # Window close button clicked?
end=True
if ev.type == pygame.KEYDOWN:
if ev.key == pygame.K_DOWN and opt==1: #moving down an option
opt=opt+1
print 2
if ev.key == pygame.K_DOWN and opt==2: #moving from last option (selection restarts at top)
opt=1
if ev.key == pygame.K_KP_ENTER and opt==1: #selecting START
pong()
if ev.key == pygame.K_DOWN and opt==1: #selecting INSTRUCTIONS
intsructions()
pygame.quit()
menu()
I don't see you defining your surface anywhere.
Just define it.