Z Ordering Controls, collision detection on top and bottom of control - python

I'm working on creating controls in pygame (button and label are the only ones done, as well as setting a tooltip for the control). I'm drawing the controls in the correct Z order but I'm trying to detect that the mouse is not over another control, so it only activates the one that's visible.
This, somewhat, works if you remove test.set_on_bottom(btnButton7), it will only trigger button7 even if the mouse is over one of the buttons below it, but if you click on one of the buttons below button7 and move the mouse over button7 it still thinks that the original button is being clicked (when it shouldn't)
Been wrapping my brain around this problem for a few days now and I just can't see to figure it out.
(Also, I just thought about it this morning but I should have turned each of the controls into a class instead of an id, so this will get reworked later)
The code is too long to post, here's the pastebin link
All of the processing of setting the states and triggering the messages is done in the process_events method
def process_events(self):
button = pygame.mouse.get_pressed()
mouse_pos = pygame.mouse.get_pos()
for control_id in reversed(self.__z_order):
state = self.__control_list[control_id]['state']
if state in ('deleted', 'disabled') or not self.__control_list[control_id]['draw']:
continue
if self.__control_list[control_id]['rect'].collidepoint(mouse_pos):
# left mouse is pressed
if button[0]:
# current state of this control is hot and left mouse is pressed on it
if state == 'hot':
for x in self.__z_order[0:control_id - 1]:
if self.__control_list[x]['state'] == 'pressed':
self.__control_list[x]['state'] = 'normal'
can_change = True
for x in self.__z_order[control_id + 1:]:
if self.__control_list[x]['state'] == 'pressed':
can_change = False
break
# change the state to pressed
if can_change:
self.__control_list[control_id]['state'] = 'pressed'
self.__control_list[control_id]['mouse_pos_lclick'] = None
self.__control_list[control_id]['mouse_pos_ldown'] = mouse_pos
if self.__control_list[control_id]['on_hover_called']:
self.__draw_tip = None
if (time.clock() - self.__control_list[control_id][
'dbl_timer'] >= self.__dbl_click_delay) and \
(time.clock() - self.__control_list[control_id]['timer'] <= self.__dbl_click_speed):
if self.__event_mode:
if self.__control_list[control_id]['on_dbl_lclick']:
self.__control_list[control_id]['on_dbl_lclick']()
else:
self.__messages.append(self.Message(control_id, PGC_LBUTTONDBLCLK))
# print('Double click', self.__control_list[control_id]['text'])
self.__control_list[control_id]['dbl_timer'] = time.clock()
self.__control_list[control_id]['timer'] = -1
break
# go through the controls from top to bottom first
for control_id in reversed(self.__z_order):
state = self.__control_list[control_id]['state']
if state in ('deleted', 'disabled') or not self.__control_list[control_id]['draw']:
continue
# check if the mouse is over this control
if self.__control_list[control_id]['rect'].collidepoint(mouse_pos):
# left mouse is not down
if not button[0]:
# state is currently pressed
if state == 'pressed':
# check if there's a timer initiated for this control
# this prevents 2 clicks + a double click message
if self.__control_list[control_id]['timer'] >= 0:
self.__control_list[control_id]['dbl_timer'] = -1
self.__control_list[control_id]['timer'] = time.clock()
# if the event mode
if self.__event_mode:
# call the function if there is one
if self.__control_list[control_id]['on_lclick']:
self.__control_list[control_id]['on_lclick']()
else:
# post the message to the messages queue
self.__messages.append(self.Message(control_id, PGC_LBUTTONUP))
# print('Click', self.__control_list[control_id]['text'])
# the timer is < 0 (should be -1), double click just happened
else:
# reset the timer to 0 so clicking can happen again
self.__control_list[control_id]['timer'] = 0
# go through all of the ids below this control
for x in self.__z_order[0:control_id - 1]:
# set all the hot controls to normal
if self.__control_list[x]['state'] == 'hot':
self.__control_list[x]['state'] = 'normal'
can_change = True
# go through all the controls on top of this control
for x in self.__z_order[control_id + 1:]:
# something else is on top of this and it's already hot, can't change this control
if self.__control_list[x]['state'] == 'hot':
can_change = False
break
if can_change:
self.__control_list[control_id]['state'] = 'hot'
self.__control_list[control_id]['mouse_pos_lclick'] = mouse_pos
self.__control_list[control_id]['mouse_pos_ldown'] = None
# state is not currently hot (but we're hovering over this control)
elif state != 'hot':
# check for any other contorls
for x in self.__z_order[0:control_id - 1]:
if self.__control_list[x]['state'] == 'hot':
self.__control_list[x]['state'] = 'normal'
can_change = True
for x in self.__z_order[control_id + 1:]:
if self.__control_list[x]['state'] == 'hot':
can_change = False
break
# change the state to hot
if can_change:
self.__control_list[control_id]['state'] = 'hot'
self.__control_list[control_id]['mouse_pos_hover'] = mouse_pos
# used to start a tooltip (needs work)
self.__control_list[control_id]['mouse_pos_rect'] = pygame.Rect(mouse_pos[0] - 7,
mouse_pos[1] - 7,
mouse_pos[0] + 7,
mouse_pos[1] + 7)
# state is currently 'hot'
else:
# timer for on_hover hasn't been initialized
if self.__control_list[control_id]['timer_on_hover'] == 0:
self.__control_list[control_id]['timer_on_hover'] = time.clock()
# mouse is in the area
if self.__control_list[control_id]['mouse_pos_rect'].collidepoint(mouse_pos):
# if the on_hover hasn't been triggered and there is a timer for the on_hover
if not self.__control_list[control_id]['on_hover_called'] and self.__control_list[control_id]['timer_on_hover']:
# if the mouse has been in the hover area for 1.5 seconds or more
if time.clock() - self.__control_list[control_id]['timer_on_hover'] >= 1.5:
# trigger the hover
self.__control_list[control_id]['on_hover_called'] = True
# on_hover is a function call, call the function
if self.__control_list[control_id]['on_hover']['type'] == 'function':
self.__control_list[control_id]['on_hover']['func'](self.__control_list[control_id]['on_hover']['args'])
# on_hover is a tip, set the self.__draw_tip variable to the tip we need
else:
self.__draw_tip = self.__control_list[control_id]['on_hover'].copy()
self.__draw_tip['rect'].x = mouse_pos[0]
self.__draw_tip['rect'].y = mouse_pos[1]
# mouse is not in the control rect and the state is not currently normal
elif state != 'normal':
# set it to normal
self.__control_list[control_id]['state'] = 'normal'
# clear the on_hover stuff
if self.__control_list[control_id]['on_hover_called']:
self.__control_list[control_id]['on_hover_called'] = False
self.__draw_tip = None
if self.__control_list[control_id]['timer_on_hover']:
self.__control_list[control_id]['timer_on_hover'] = 0

(Will select as the correct answer tomorrow when the 24hr timeout is over)
Ended up figuring it out. I was way over complicating it.
Go through the list of controls in reverse, the first one that the .collidepoint succeeded on, save that control and break the loop
Go through all of the controls and one that's not disabled and isn't currently normal, set to normal
Work with the control that has been saved as needed
def process_events(self):
button = pygame.mouse.get_pressed()
mouse_pos = pygame.mouse.get_pos()
top_id = -1
for control_id in reversed(self.__z_order):
if self.__control_list[control_id]['state'] != 'disabled' and \
self.__control_list[control_id]['draw'] and \
self.__control_list[control_id]['rect'].collidepoint(mouse_pos):
top_id = control_id
break
if top_id != -1:
# go through all of the controls
for control_id in self.__z_order:
# skip the top most control and any that are disabled/deleted
if self.__control_list[control_id]['state'] != 'disabled' and \
self.__control_list[control_id]['state'] != 'normal' and \
control_id != top_id:
# set it to normal
self.__control_list[control_id]['state'] = 'normal'
# clear the on_hover stuff
if self.__control_list[control_id]['on_hover_called']:
self.__control_list[control_id]['on_hover_called'] = False
self.__draw_tip = None
if self.__control_list[control_id]['timer_on_hover']:
self.__control_list[control_id]['timer_on_hover'] = 0
else:
for control_id in self.__z_order:
if self.__control_list[control_id]['state'] != 'disabled':
# set it to normal
self.__control_list[control_id]['state'] = 'normal'
# clear the on_hover stuff
if self.__control_list[control_id]['on_hover_called']:
self.__control_list[control_id]['on_hover_called'] = False
self.__draw_tip = None
if self.__control_list[control_id]['timer_on_hover']:
self.__control_list[control_id]['timer_on_hover'] = 0
return
if button[0]:
# current state of this control is hot and left mouse is pressed on it
if self.__control_list[top_id]['state'] == 'hot':
self.__control_list[top_id]['state'] = 'pressed'
self.__control_list[top_id]['mouse_pos_lclick'] = None
self.__control_list[top_id]['mouse_pos_ldown'] = mouse_pos
if self.__control_list[top_id]['on_hover_called']:
self.__draw_tip = None
if (time.clock() - self.__control_list[top_id][
'dbl_timer'] >= self.__dbl_click_delay) and \
(time.clock() - self.__control_list[top_id]['timer'] <= self.__dbl_click_speed):
if self.__event_mode:
if self.__control_list[top_id]['on_dbl_lclick']:
self.__control_list[top_id]['on_dbl_lclick']()
else:
self.__messages.append(self.Message(top_id, PGC_LBUTTONDBLCLK))
# print('Double click', self.__control_list[top_id]['text'])
self.__control_list[top_id]['dbl_timer'] = time.clock()
self.__control_list[top_id]['timer'] = -1
elif not button[0]:
# state is currently pressed
if self.__control_list[top_id]['state'] == 'pressed':
# check if there's a timer initiated for this control
# this prevents 2 clicks + a double click message
if self.__control_list[top_id]['timer'] >= 0:
self.__control_list[top_id]['dbl_timer'] = -1
self.__control_list[top_id]['timer'] = time.clock()
# if the event mode
if self.__event_mode:
# call the function if there is one
if self.__control_list[top_id]['on_lclick']:
self.__control_list[top_id]['on_lclick']()
else:
# post the message to the messages queue
self.__messages.append(self.Message(top_id, PGC_LBUTTONUP))
# print('Click', self.__control_list[top_id]['text'])
# the timer is < 0 (should be -1), double click just happened
else:
# reset the timer to 0 so clicking can happen again
self.__control_list[top_id]['timer'] = 0
# go through all of the ids below this control
for x in self.__z_order[0:top_id - 1]:
# set all the hot controls to normal
if self.__control_list[x]['state'] == 'hot':
self.__control_list[x]['state'] = 'normal'
can_change = True
# go through all the controls on top of this control
for x in self.__z_order[top_id + 1:]:
# something else is on top of this and it's already hot, can't change this control
if self.__control_list[x]['state'] == 'hot':
can_change = False
break
if can_change:
self.__control_list[top_id]['state'] = 'hot'
self.__control_list[top_id]['mouse_pos_lclick'] = mouse_pos
self.__control_list[top_id]['mouse_pos_ldown'] = None
# state is not currently hot (but we're hovering over this control)
elif self.__control_list[top_id]['state'] != 'hot':
self.__control_list[top_id]['state'] = 'hot'
self.__control_list[top_id]['mouse_pos_hover'] = mouse_pos
# used to start a tooltip (needs work)
self.__control_list[top_id]['mouse_pos_rect'] = pygame.Rect(mouse_pos[0] - 7,
mouse_pos[1] - 7,
mouse_pos[0] + 7,
mouse_pos[1] + 7)
# state is currently 'hot'
else:
# timer for on_hover hasn't been initialized
if self.__control_list[top_id]['timer_on_hover'] == 0:
self.__control_list[top_id]['timer_on_hover'] = time.clock()
# mouse is in the area
if self.__control_list[top_id]['mouse_pos_rect'].collidepoint(mouse_pos):
# if the on_hover hasn't been triggered and there is a timer for the on_hover
if not self.__control_list[top_id]['on_hover_called'] and \
self.__control_list[top_id]['timer_on_hover']:
# if the mouse has been in the hover area for 1.5 seconds or more
if time.clock() - self.__control_list[top_id]['timer_on_hover'] >= 1.5:
# trigger the hover
self.__control_list[top_id]['on_hover_called'] = True
# on_hover is a function call, call the function
if self.__control_list[top_id]['on_hover']['type'] == 'function':
self.__control_list[top_id]['on_hover']['func'] \
(self.__control_list[top_id]['on_hover']['args'])
# on_hover is a tip, set the self.__draw_tip variable to the tip we need
else:
self.__draw_tip = self.__control_list[top_id]['on_hover'].copy()
self.__draw_tip['rect'].x = mouse_pos[0]
self.__draw_tip['rect'].y = mouse_pos[1]

Related

Pygame freezes when for loop is running even when using pygame.event.pump() [duplicate]

This question already has answers here:
Pygame window not responding after a few seconds
(3 answers)
Closed 5 months ago.
I'm currently making a rhythm game in Pygame and I have been trying to output "notes", but in my sprite class they are called enemies for a separate reason, based on the current time being the same(ish) to floats in a text file. However when I try to use the loop whether it be an infinite for loop or a while loop it cause the window to freeze whilst it's activated. I have tried pygame.event.pump() but that just makes it so that after the loop has ended it is no longer frozen.
Anything commented is what I have tried already.
import pygame
from sys import exit
class Enemies(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load(r'\slime.png').convert_alpha()
self.image = pygame.transform.rotozoom(self.image,0,4)
self.rect = self.image.get_rect(center = (1040,-50))
# def d_notes(self):
# keys = pygame.key.get_pressed()
# if keys[pygame.K_SPACE]:
# maps = beat_read()
# temp = list(maps)
# note = 0
# p_note = 0
# for x in iter(int, 1):
# pygame.event.pump()
# pygame.event.pump()
# if p_note == len(maps):
# break
# current_time = (pygame.time.get_ticks() / 1000) - start_time
# x_lower = maps[note] - 0.02
# x_upper = maps[note] + 0.02
# if x_lower <= current_time <= x_upper:
# out = temp.pop(0)
# print(out)
# note = note + 1
# p_note = p_note + 1
def update(self):
self.rect.y +=5
#self.d_notes()
def beat_read():
with open(r'\simple.txt') as f:
ll = []
for x in f:
x = float(x)
ll.append(x)
ll = tuple(ll)
return ll
# def d_notes():
# maps = beat_read()
# temp = list(maps)
# note = 0
# p_note = 0
# for x in iter(int,1):
# pygame.event.pump()
# pygame.event.pump()
# if p_note == len(maps):
# break
# current_time = (pygame.time.get_ticks() / 1000) - start_time
# x_lower = maps[note] - 0.02
# x_upper = maps[note] + 0.02
# if x_lower <= current_time <= x_upper:
# out = temp.pop(0)
# print(out)
# note = note + 1
# p_note = p_note + 1
# print(note)
# print(maps)
# print(temp)
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((1366,850))
pygame.display.set_caption('Rhythmic')
game_active = False
enemies_group = pygame.sprite.Group()
enemies_group.add(Enemies())
background = pygame.image.load(r'\background.png').convert()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
game_active = True
start_time = pygame.time.get_ticks() / 1000
# maps = beat_read()
# temp = list(maps)
# note = 0
# p_note = 0
# for x in iter(int, 1):
# pygame.event.pump()
# pygame.event.pump()
# if p_note == len(maps):
# break
# current_time = (pygame.time.get_ticks() / 1000) - start_time
# x_lower = maps[note] - 0.02
# x_upper = maps[note] + 0.02
# if x_lower <= current_time <= x_upper:
# out = temp.pop(0)
# print(out)
# note = note + 1
# p_note = p_note + 1
if game_active:
# d_notes()
screen.blit(background,(0,0))
enemies_group.draw(screen)
enemies_group.update()
pygame.display.flip()
clock.tick(60)
[Aside: User input handling does not belong in a Sprite class. ]
The for x in iter(int, 1): is an infinite loop, is this your intention?
So the code is:
If space was pressed:
Loop forever, doing:
touch the event handler
if the timing calculation is correct:
advance to next beat
print some stuff
check if we should exit the loop
I would say your timing-calculation check is never-ok.
Assuming you're writing some kind of follow-the-beat game, don't you need to repeatedly check for the time of the key-press, then check against the beat-time?
Also you need to advance the counters when the key-press has incorrect timing. I think this is the #1 reason it's getting stuck in the loop.
Probably you could (with a pencil and paper), re-work your control loop.
Load some beat-files.
Main Loop:
For every event:
Does the event say [space] was pressed?
Make a note of the press-timing
Do we have any beats left?:
Is it time for the next beat:
Advance to the next beat
else:
Show Game Over
Use the next beat-file, or whatever
Update any screen elements
Paint the screen
Was [space] pressed and is the timing accurate:
Increase player score (or whatever)

Autoclicker that clicks left click and uses left click as the hot key

This seems really simple but, I realized the issue is that when pyautogui left clicks the key state is reset to up even though I may still be holding down left click.
import win32api
import pyautogui
state_left = win32api.GetKeyState(0x01)
pyautogui.PAUSE = 0.06
stop_key_state = win32api.GetKeyState(0x50)
while True:
a = win32api.GetKeyState(0x01)
b = win32api.GetKeyState(0x50)
if b < 0:
False
if a != state_left: # Button state changed
state_left = a
if a < 0:
while True:
a = win32api.GetKeyState(0x01)
if a < 0:
pyautogui.click()
#print('Left Button Pressed')
else:
False
else:
print('Left Button Released')

Change the Mouse click detection of Pygame to a serial Input

is it possible to change the mouse-click detection of Pygame to an external input for example?
Im trying to build a chess code, which is receiving Positions from an Arduino. So in the following Code im trying to convert my input string (b2) to the Position (2,2) and give it back in my main as the x,y information for pygame.
def objectdetection(screen, game_state, valid_moves, square_selected):
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
ser.flush()
while True:
if ser.in_waiting > 0:
Position = [ser.readline().decode('utf-8').rstrip()]
if Position == "b2":
return (2, 2)
main:
elif e.type == objectdetection:
if not game_over and human_turn:
location = objectdetection() # (x, y) location of the mouse
col = location[0] // SQUARE_SIZE
row = location[1] // SQUARE_SIZE
if square_selected == (row, col) or col >= 8: # user clicked the same square twice
square_selected = () # deselect
player_select = [] # clear clicks
else:
square_selected = (row, col)
player_select.append(square_selected) # append for both 1st and 2nd click
if len(player_select) == 2: # after 2nd click
move = ChessEngine.Move(player_select[0], player_select[1],game_state.board)
for i in range(len(valid_moves)):
if move == valid_moves[i]:
game_state.makeMove(valid_moves[i])
move_made = True
animate = True
square_selected = () # reset user clicks
player_select = []
if not move_made:
player_select = [square_selected]
sadly it doesn't work so easy. Is it overall possible to change the Mouse detection from pygame to an external input?
Thanks for any help.
Write function for the object selection and put the code in the function:
def select(row, col)
global square_selected, player_select, move, move_made, animate
if square_selected == (row, col) or col >= 8: # user clicked the same square twice
square_selected = () # deselect
player_select = [] # clear clicks
else:
square_selected = (row, col)
player_select.append(square_selected) # append for both 1st and 2nd click
if len(player_select) == 2: # after 2nd click
move = ChessEngine.Move(player_select[0], player_select[1],game_state.board)
for i in range(len(valid_moves)):
if move == valid_moves[i]:
game_state.makeMove(valid_moves[i])
move_made = True
animate = True
square_selected = () # reset user clicks
player_select = []
if not move_made:
player_select = [square_selected]
You can call the function when the mouse is clicked:
for e in p.event.get():
if e.type == p.QUIT:
# [...]
elif e.type == p.MOUSEBUTTONDOWN:
if not game_over and human_turn:
col = e.pos[0] // SQUARE_SIZE
row = e.pos[1] // SQUARE_SIZE
select(row, col)
And you can call the function when you receive a position:
position = objectdetection(.....)
if position != None:
row = position[0]
col = position[1]
select(row, col)

Calling button only ticks for one second and can't display text?

I am making a game and I want when I am in the garage if I buy more speed and I am already max, a text to pop up saying "Max speed reached!". But when I click on the button and have max speed, it doesn't stay but goes away after 1 tick. I can't figure out how to make it stay for longer because of how the button works. Any help is appreciated.
Here is the code for the button:
elif event.type == pygame.MOUSEBUTTONDOWN:
# 1 is the left mouse button, 2 is middle, 3 is right.
if event.button == 1:
# `event.pos` is the mouse position.
if button2.collidepoint(event.pos):
print('we been clicked')
current_time = time.strftime("%S")
# Increment the number
if player.vel < 20:
if player.coins >= 50:
player.vel += 5
player.coins -= 50
print('speed =' + str(player.vel))
player.vel1 = player.vel
player.grassvel = player.vel // 2
player.save()
else:
print("ur poor")
else:
MaxSpeedReached()
And here is the code for the function it calls:
def MaxSpeedReached():
display_crash_text = False
print("Max speed reached")
if display_crash_text == False:
start_time = time.strftime("%S")
display_crash_text = True
maxspeedtext = pygame.font.Font("freesansbold.ttf", 20)
maxspedd, maxspeedr = text_objects("Max Speed Reached!", maxspeedtext, BLACK)
maxspeedr.center = ((205, 270))
print(current_time, start_time)
if display_crash_text == True:
win.blit(maxspedd, maxspeedr)
if int(start_time) - int(current_time) < 3:
display_crash_text = False
print("yo we checking the time difference")
Obviously you're seeing this because the message is drawn once when MaxSpeedReached() is called, but the only possible path to reach this function is on pygame.MOUSEBUTTONDOWN event.
You need to paint the message in the main loop, whenever the conditions for max-speed are occurring.
MAX_VELOCITY = 20
maxspeedtext = pygame.font.Font("freesansbold.ttf", 20)
maxspedd, maxspeedr = text_objects("Max Speed Reached!", maxspeedtext, BLACK)
maxspeedr.center = ((205, 270))
[ ... ]
# Main loop
while not finished:
[...]
if ( player.vel >= MAX_VELOCITY ):
win.blit( maxspedd, maxspeedr )

Python - if statement returning True when I don't want it to

The code I've just written is used so that it adjusts the value of each item in a list each time the end users scrolls, so that essentially the value of each item matches the y coordinate of its corresponding object when blitted on to the surface.
The logic behind my code looks at whether or not the difference between the original y coordinate and the new y coordinate of a object when scrolled remains the same, if not then it should proceed to the for loop, else move on the code following the if statement.
For some reason, the if statement keeps returning True even though there is no difference between the two control variables I am using.
I am not sure if I am overlooking something subtle which means that it will keep returning True or my logic just hasn't worked the way I expected it to; my bet is on the latter.
#example y values in list
yIndex = [122, 152, 212, 242]
scroll_y = 63 #initial y scroll coordinate
originalScroll_y = 63
while True:
for event in pygame.event.get():
#code checking whether user has scrolled
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 4 and scroll_y != 63:
originalScroll_y = scroll_y
scroll_y = min(scroll_y + 15, 63)
if event.button == 5:
originalScroll_y = scroll_y
scroll_y = max(scroll_y - 15, finalY + 470)
elif event.type == MOUSEBUTTONUP and event.button == 1 and isinstance(page, MainPage):
x, y = pygame.mouse.get_pos()
control = originalScroll_y - scroll_y
control2 = 0
#e.g. if control = 15 and control2 = 15, it returns True instead
#of False
if control != control2:
for k, j in enumerate(yIndex):
j -= control
yIndex[k] = j
control2 = control
for i in yIndex:
if y >= (i - 10) and y <= (i + 20):
if i in indexedContacts:
buttonSound.play()
sleep(0.5)
scroll_y = 63
page = EditPage()
page.style()
page.contactFields()
break
You have a nested if statement so control2 is set to 0
elif event.type == MOUSEBUTTONUP and event.button == 1 and isinstance(page, MainPage):
x, y = pygame.mouse.get_pos()
control = originalScroll_y - scroll_y
control2 = 0 # set to 0 here
if control != control2: # is 0 here
Control2 will never be 15 or any value other than 0 in that if statement.

Categories