I'm creating a Chess clock for my chess project. In chess there are time formats 5|5 is where each player (white and black) is granted a general 5 minutes, so when either player makes a move it adds 5 seconds to the clock. So 15|10 is 15 minutes in general and 10 seconds per move.
My code:
import pygame
import time
pygame.init()
size = width, height = 300, 300
screen = pygame.display.set_mode(size)
# Fonts
Font = pygame.font.SysFont("Trebuchet MS", 25)
class Button:
def __init__(self, color, x, y, width, height, text=''):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def draw(self, screen, outline=None):
# Call this method to draw the button on the screen
if outline:
pygame.draw.rect(screen, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('freesansbold.ttf', 16)
text = font.render(self.text, 1, (0, 0, 0))
screen.blit(text, (
self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def setColor(self, new_color):
self.color = new_color
def isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
if self.x < pos[0] < self.x + self.width:
if self.y < pos[1] < self.y + self.height:
return True
return False
def main():
global Second, Minute
running = True
Black = 0, 0, 0
White = 255, 255, 255
Second = 0
Minute = 0
five_five = False
Clock = pygame.time.Clock()
while running:
five_five_button = Button(White, 150, 150, 40, 20, '5|5')
five_plus_five = Button(White, 150, 180, 40, 20, "+5 secs")
five_five_button.draw(screen, None)
five_plus_five.draw(screen, None)
pygame.display.update()
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
if e.type == pygame.MOUSEBUTTONDOWN:
if five_five_button.isOver(e.pos):
five_five = True
if five_plus_five.isOver(e.pos):
Second = +5
if e.type == pygame.KEYDOWN:
pass # if you want to add keys to test things easier
if five_five:
Minute = 5
Second = 0
five_five = False
if not five_five:
if Second == 0:
Second = 58
Minute = Minute - 1
Second += 2
time.sleep(1)
Second -= 1
# Minute
MinuteFont = Font.render(str(Minute).zfill(2), True, Black)
MinuteFontR = MinuteFont.get_rect()
MinuteFontR.center = (50, 60)
# Second
SecondFont = Font.render(":" + str(Second).zfill(2), True, Black)
SecondFontR = SecondFont.get_rect()
SecondFontR.center = (80, 60)
screen.fill(White)
# Timer
# while Time==0: this will cause a crash !
screen.blit(SecondFont, SecondFontR)
screen.blit(MinuteFont, MinuteFontR)
pygame.display.flip()
Clock.tick(60)
if __name__ == '__main__':
main()
The issues I face:
So let's say the player makes a move and the time is at 4:59. We want to add 5 secs. So it adds 5 seconds so 4:64 so it'll minus a minute so 3:04. I don't know how to fix that.
Another is that whenever you want to add 5 seconds or whatever it'll never goes past 59 and add those seconds on to the total and add a minute.
I've worked hours on this believe it or not figuring out the simple things. I've looked up how to make clocks and things in python (or pygame) and I always get adding clocks. I never found a clock that adds time, but also counts down at the same time.
Whenever I even try to load or click one of the buttons. It takes a few seconds to register it. So if you can help me figure this all out or find out a better way to make it I appreciate it a lot.
Name changes
First of all, I would rename some variables to better convey their meaning (given that some weren't immediately obvious), so I changed these:
five_five_button -> reset_button
five_plus_five -> add_button
Second -> seconds
Minute -> minutes
Black -> BLACK
White -> WHITE
Button creation
Next, I don't think you need to re-create your two buttons every frame. Just once should be enough to enable them to be drawn, so I moved them as shown:
# moved to here
reset_button = Button(White, 150, 150, 40, 20, '5|5')
add_button = Button(White, 150, 180, 40, 20, "+5 secs")
while running:
# removed from here
# reset_button = Button(White, 150, 150, 40, 20, '5|5')
# add_button = Button(White, 150, 180, 40, 20, "+5 secs")
# ... rest of loop code ...
Minutes and seconds timer
Next, I like what you were going for with the reset and adding time code, but think in a scenario like this it actually just makes things more complicated. So I removed these if statements:
if five_five:
minutes = 5
seconds = 0
five_five = False
if not five_five:
if seconds == 0:
seconds = 58
minutes = minutes - 1
seconds += 2
and moved their logic straight into the pygame event handling loop code:
if e.type == pygame.MOUSEBUTTONDOWN:
if reset_button.isOver(e.pos):
minutes = 5
seconds = 0
if add_button.isOver(e.pos):
seconds += 5
but this also means that we don't have any logic to handle what happens if the seconds goes "out of bounds", so I wrote the following to happen right after our pygame event loop:
while seconds >= 60:
seconds -= 60
minutes += 1
while seconds < 0:
seconds += 60
minutes -= 1
Splitting into functions
The refactored code is almost complete, but before that I want to split our code into functions to help maintainability and modularilty. I also really like the use of classes for the buttons.
First I want a "draw everything on the screen" function:
def draw_screen():
global WHITE, BLACK
screen.fill(WHITE)
# buttons
reset_button.draw(screen, None)
add_button.draw(screen, None)
# minutes tezt
minutesFont = Font.render(str(minutes).zfill(2), True, BLACK)
minutesFontR = minutesFont.get_rect()
minutesFontR.center = (50, 60)
screen.blit(minutesFont, minutesFontR)
# seconds text
secondsFont = Font.render(":" + str(seconds).zfill(2), True, BLACK)
secondsFontR = secondsFont.get_rect()
secondsFontR.center = (80, 60)
screen.blit(secondsFont, secondsFontR)
pygame.display.flip()
Timer
The reason you feel that the buttons take forever to respond is because pygame can only respond to button presses once a second. This is because of your time.sleep(1), meaning that your event loop is only run once a second. We will need a non-blocking way of decreasing the seconds timer by 1 once a second:
previousTime = time.time()
# ...
while running:
# ...
if time.time() - previousTime > 1:
previousTime = time.time()
seconds -= 1
Final code
import pygame
import time
pygame.init()
size = width, height = 300, 300
screen = pygame.display.set_mode(size)
# Fonts
Font = pygame.font.SysFont("Trebuchet MS", 25)
class Button:
def __init__(self, color, x, y, width, height, text=''):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def draw(self, screen, outline=None):
# Call this method to draw the button on the screen
if outline:
pygame.draw.rect(screen, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('freesansbold.ttf', 16)
text = font.render(self.text, 1, (0, 0, 0))
screen.blit(text, (
self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def setColor(self, new_color):
self.color = new_color
def isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
if self.x < pos[0] < self.x + self.width:
if self.y < pos[1] < self.y + self.height:
return True
return False
def draw_screen(minutes, seconds):
global WHITE, BLACK
screen.fill(WHITE)
# buttons
reset_button.draw(screen, None)
add_button.draw(screen, None)
# minutes tezt
minutesFont = Font.render(str(minutes).zfill(2), True, BLACK)
minutesFontR = minutesFont.get_rect()
minutesFontR.center = (50, 60)
screen.blit(minutesFont, minutesFontR)
# seconds text
secondsFont = Font.render(":" + str(seconds).zfill(2), True, BLACK)
secondsFontR = secondsFont.get_rect()
secondsFontR.center = (80, 60)
screen.blit(secondsFont, secondsFontR)
pygame.display.flip()
def fix_time(minutes, seconds):
while seconds >= 60:
seconds -= 60
minutes += 1
while seconds < 0:
seconds += 60
minutes -= 1
return minutes, seconds
def handle_events(minutes, seconds, reset_button, add_button):
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
if e.type == pygame.MOUSEBUTTONDOWN:
if reset_button.isOver(e.pos):
minutes = 5
seconds = 0
if add_button.isOver(e.pos):
seconds += 5
return minutes, seconds
def main():
global seconds, minutes
running = True
BLACK = 0, 0, 0
WHITE = 255, 255, 255
seconds = 0
minutes = 0
previousTime = time.time()
Clock = pygame.time.Clock()
reset_button = Button(WHITE, 150, 150, 40, 20, '5|5')
add_button = Button(WHITE, 150, 180, 40, 20, "+5 secs")
while running:
minutes, seconds = handle_events(minutes, seconds, reset_button, add_button)
if time.time() - previousTime > 1:
previousTime = time.time()
seconds -= 1
minutes, seconds = fix_time(minutes, seconds)
draw_screen(minutes, seconds):
Clock.tick(60)
if __name__ == '__main__':
main()
Thoughts
I haven't run any of the code, but the thought, intent and improvement is still just as valid. There are still improvements that could be made to the code (having to return minutes and seconds, and passing reset_button and add_button as parameters, are both "not great") so feel free to use my example as inspiration.
So let's just rebuild the block where you handle minute changes.
Things we need to implement:
Every tick, needs to check if seconds has gone over 59 or under 0
If over, then we need to add a minute
If under, then we need to subtract a minute
In both cases, we need to reset the seconds
We can do all that with simple if statements. Have a look at the following.
I am also going to move the seconds tick to be before the check, to make our math a bit more clear (Change the time, and then fix it)
Second -= 1
if not five_five:
if Second < 0:
Minute -= 1
Second += 60 # Since Seconds will probably be -1
elif Second > 59:
Minute += 1
Second -= 60
Related
This question is really difficult to ask, but I know you guys here at Stack Overflow are the brightest minds.
I'm totally blinded by why this issue happens (I'm fairly at Python and Pygame, so any suggestions on how to improve the code will be received with the love of improving my skills).
What I'm creating:
It's really a gimmick project, I have a little 2.5" screen (PiTFT) attached to a Raspberry Pi and the code is creating a typewriter effect with a moving cursor in front of the text as it's being written.
Challenge 1 was that every time you move a sprite in pygame, you must redraw everything, otherwise you will see a trail, and since the cursor is moving in front of the text, the result would look like this:
I managed to solve this issue by blackening / clearing the screen. But then I lost all the previously written letters.
So I created a list (entireword), which I'm populing with all the previously written characters. I use this list every time I cycle through the loop to redraw all the previous written text.
So now:
As you can see, the text looks funny.
It's supposed to read:
[i] Initializing ...
[i] Entering ghost mode ... []
I've been spending hours and hours getting to this point - and the code ALMOST works perfectly! The magic happens in the function print_screen(), but WHAT in my code is causing the text to include a letter from the other line in the end? :>
Help is GREATLY appreciated <3
Here's the entire code:
import pygame
import time
import os
import sys
from time import sleep
from pygame.locals import *
positionx = 10
positiony = 10
entireword = []
entireword_pos = 10
counter = 0
entire_newline = False
#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
speed = 0.05
#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
myfont = pygame.font.SysFont("monospace", 18)
#Class
class cursors(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill((0,255,0))
self.rect = self.image.get_rect()
self.rect.center = (positionx + 10, positiony + 10)
def update(self):
self.rect.x = positionx + 10
self.rect.y = positiony
#Functions
#Prints to the screen
def print_screen(words, speed):
rel_speed = speed
for char in words:
#speed of writing
if char == ".":
sleep(0.3)
else:
sleep(rel_speed)
#re-renders previous written letters
global entireword
# Old Typewriter functionality - Changes position of cursor and text a newline
#Makes sure the previous letters are rendered and not lost
#xx is a delimter so the program can see when to make a newline and ofcourse ignore writing the delimiter
entireword.append(char)
if counter > 0:
loopcount = 1
linecount = 0 # This is to which line we are on
for prev in entireword:
if prev == 'xx':
global linecount
global positiony
global loopcount
linecount = linecount + 1
positiony = 17 * linecount
loopcount = 1
if prev != 'xx': #ignore writing the delimiter
pchar = myfont.render(prev, 1, (255,255,0))
screen.blit(pchar, (loopcount * 10, positiony))
loopcount = loopcount + 1
if char != 'xx':
# render text
letter = myfont.render(char, 1, (255,255,0))
#blits the latest letter to the screen
screen.blit(letter, (positionx, positiony))
# Appends xx as a delimiter to indicate a new line
if entire_newline == True:
entireword.append('xx')
global entire_newline
entire_newline = False
global positionx
positionx = positionx + 10
all_sprites.update()
all_sprites.draw(screen)
pygame.display.flip()
screen.fill((0,0,0)) # blackens / clears the screen
global counter
counter = counter + 1
#Positions cursor at new line
def newline():
global positionx
global positiony
positionx = 10
positiony = positiony + 17
all_sprites = pygame.sprite.Group()
cursor = cursors()
all_sprites.add(cursor)
#Main loop
running = True
while running:
global speed
global entire_newline
words = "[i] Initializing ..."
entire_newline = True
newline()
print_screen(words,speed)
words = "[i] Entering ghost mode ..."
entire_newline = True
newline()
print_screen(words,speed)
#Stops the endless loop if False
running = False
sleep(10)
Sorry if I don't answer your question directly, because your code is too confusing for me now, so I took the liberty to rewrite your code to get done what you want.
The idea is to have two sprites:
the cursor, which is a) displayed on the screen and b) keeps track of what text to write and where
the board, which is basically just a surface that the text is rendered on
Note how all the writing logic is on the Cursor class, and we have a nice, simple and dumb main loop.
import pygame
import os
#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
clock = pygame.time.Clock()
#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
class Board(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((WIDTH, HEIGHT))
self.image.fill((13,13,13))
self.image.set_colorkey((13,13,13))
self.rect = self.image.get_rect()
self.font = pygame.font.SysFont("monospace", 18)
def add(self, letter, pos):
s = self.font.render(letter, 1, (255, 255, 0))
self.image.blit(s, pos)
class Cursor(pygame.sprite.Sprite):
def __init__(self, board):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill((0,255,0))
self.text_height = 17
self.text_width = 10
self.rect = self.image.get_rect(topleft=(self.text_width, self.text_height))
self.board = board
self.text = ''
self.cooldown = 0
self.cooldowns = {'.': 12,
'[': 18,
']': 18,
' ': 5,
'\n': 30}
def write(self, text):
self.text = list(text)
def update(self):
if not self.cooldown and self.text:
letter = self.text.pop(0)
if letter == '\n':
self.rect.move_ip((0, self.text_height))
self.rect.x = self.text_width
else:
self.board.add(letter, self.rect.topleft)
self.rect.move_ip((self.text_width, 0))
self.cooldown = self.cooldowns.get(letter, 8)
if self.cooldown:
self.cooldown -= 1
all_sprites = pygame.sprite.Group()
board = Board()
cursor = Cursor(board)
all_sprites.add(cursor, board)
text = """[i] Initializing ...
[i] Entering ghost mode ...
done ...
"""
cursor.write(text)
#Main loop
running = True
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
all_sprites.update()
screen.fill((0, 0, 0))
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(60)
Use the pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. The time has to be set in milliseconds. e.g.:
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
Add a new letter to the text, when the timer event occurs:
while run:
for event in pygame.event.get():
# [...]
if event.type == typewriter_event:
text_len += 1
See also Typewriter
Minimal example:
repl.it/#Rabbid76/PyGame-Typewriter
import pygame
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (32, 32, 32), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
text = 'Hello World'
text_len = 0
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
text_surf = None
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == typewriter_event:
text_len += 1
if text_len > len(text):
text_len = 0
text_surf = None if text_len == 0 else font.render(text[:text_len], True, (255, 255, 128))
window.blit(background, (0, 0))
if text_surf:
window.blit(text_surf, text_surf.get_rect(midleft = window.get_rect().midleft).move(40, 0))
pygame.display.flip()
pygame.quit()
exit()
I am developing a small target shooter game for school coursework. I have hit an an impasse after about an hour and a half of iteration and testing. If you see the code below, I have used lists to make it so that when bullets hit a target, they are removed from the list and are no longer printed with 'pygame.draw.rect' but it seems to be much more difficult for the targets as I have made these using OOP and not a one off function. I have tried replicating the bullet lists with the targets, putting the 'IF' statement in the same places etc. but I end up having the same outcome which is the target is hit and 'hit' is printed. The bullet disappears but the target doesn't. I have only been using the language for about a month so although I'm getting more used to it, I still am no expert and I really have just hit a dead end here. Any sort of help would be greatly appreciated. It may be a case of me having to completely rethink my approach and change my code drastically but perhaps theres something you can see which I can not. Thanks for any help given. (Sorry for the messiness of the code, it is after a lot of changes and iterations. Also please note that I have currently only coded to test it on target_1 to save time)
import pygame
#Setting window dimensions and caption. (Module 1)
pygame.init()
window = pygame.display.set_mode((800, 575))
pygame.display.set_caption("TARGET PRACTICE")
#Colour variables. (Module 1)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (200, 0, 0)
GREEN = (0, 200, 0)
BLUE = (0, 0, 200)
#py_clock tracks framerate of program for other 'pygame.time' commands. (Module 8)
py_clock = pygame.time.Clock()
#Target class created. (Module 5)
class Target:
def __init__(self, x, y, h, w, v):
self.x = x
self.y = y
self.h = h
self.w = w
self.v = v
def hit(self):
print('hit')
all_bullets_keep.remove(item)
all_targets.remove(all_targets[0])
pass
#Instantiation of targets. (Module 5)
target_1 = Target(0, 80, 60, 40, 0.05)
target_2 = Target(0, 100, 60, 40, 0.5)
target_3 = Target(0, 50, 60, 40, 0.5)
target_4 = Target(0, 75, 60, 40, 0.5)
target_5 = Target(0, 45, 60, 40, 0.5)
target_6 = Target(0, 85, 60, 40, 0.5)
#Instantiation of hitboxes. (Module 9)
target_hbx1 = Target(-5, 75, 70, 50, 0.05)
target_hbx2 = Target(-5, 95, 70, 50, 0.5)
target_hbx3 = Target(-5, 45, 70, 50, 0.5)
target_hbx4 = Target(-5, 70, 70, 50, 0.5)
target_hbx5 = Target(-5, 40, 70, 50, 0.5)
target_hbx6 = Target(-5, 80, 70, 50, 0.5)
#Declaring variables to be used in the while loop. (Module 5)
clock = 0
target_2_threshold = 500
target_3_threshold = 1000
target_4_threshold = 1500
target_5_threshold = 2000
target_6_threshold = 2500
#Setting player sprite dimension variables. (Module 6)
player_sprite_x = 357.5
player_sprite_y = 450
player_sprite_h = 125
player_sprite_w = 85
#all_bullets list to store bullets made by function inside loop. (Module7)
all_bullets = []
all_targets = []
all_targets.append(target_1)
all_targets.append(target_2)
all_targets.append(target_3)
all_targets.append(target_4)
all_targets.append(target_5)
all_targets.append(target_6)
#Variables to track and limit shooting function. (Module 9.5)
bullet_delay = 1500
next_bullet_time = 0
exec = True
while exec:
#current_time uses a pygame_time command to track ticks. (Module 9.5)
current_time = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT:
exec = False
#'IF' statement to trigger the shooting function. (Module 7)
if event.type == pygame.MOUSEBUTTONDOWN:
#Condition set to only trigger the below code if the current_time is greater than the next_bullet time. (Module 9.5)
if event.button == 1 and current_time > next_bullet_time:
next_bullet_time = current_time + bullet_delay
dx = event.pos[0] - (player_sprite_x+ player_sprite_w//2)
dy = event.pos[1] - player_sprite_y
direction = pygame.math.Vector2(dx, dy).normalize()
bullet = {'x': player_sprite_x+42, 'y': player_sprite_y, 'direction': direction}
all_bullets.append(bullet)
#Defines movement of targets and sets delay between drawings. (Module 5)
for item in all_targets:
target_1.x += target_1.v
target_hbx1.x += target_hbx1.v
if clock > target_2_threshold:
target_2.x += target_2.v
target_hbx2.x += target_hbx2.v
if clock > target_3_threshold:
target_3.x += target_3.v
target_hbx3.x += target_hbx3.v
if clock > target_4_threshold:
target_4.x += target_4.v
target_hbx4.x += target_hbx4.v
if clock > target_5_threshold:
target_5.x += target_5.v
target_hbx5.x += target_hbx5.v
if clock > target_6_threshold:
target_6.x += target_6.v
target_hbx6.x += target_hbx6.v
#all_bullets_keep list combined with FOR loop retains only bullets in the arena. (Module 7)
all_bullets_keep = []
for item in all_bullets:
item['x'] += item['direction'][0] # item['direction'][0] * 2
item['y'] += item['direction'][1] # item['direction'][1] * 2
if 0 < item['x'] < 800 and 0 < item['y'] < 575:
all_bullets_keep.append(item)
all_bullets = all_bullets_keep
#Fill the background (Module 5)
window.fill(RED)
#Redraw each target in every frame. (Module 5)
all_targets_keep = []
for item in all_targets:
pygame.draw.rect(window, BLUE, (target_1.x, target_1.y, target_1.h, target_1.w))
pygame.draw.rect(window, BLUE, (target_hbx1.x, target_hbx1.y, target_hbx1.h,target_hbx1.w), 2)
if 0 < target_1.x < 800 and 0 < target_1.y < 575:
all_targets_keep.append(target_1)
if clock > target_2_threshold:
pygame.draw.rect(window, BLUE, (target_2.x, target_2.y, target_2.h, target_2.w))
pygame.draw.rect(window, BLUE, (target_hbx2.x, target_hbx2.y, target_hbx2.h,target_hbx2.w), 2)
all_targets_keep.append(target_2)
if clock > target_3_threshold:
pygame.draw.rect(window, BLUE, (target_3.x, target_3.y, target_3.h, target_3.w))
pygame.draw.rect(window, BLUE, (target_hbx3.x, target_hbx3.y, target_hbx3.h,target_hbx3.w), 2)
all_targets_keep.append(target_3)
if clock > target_4_threshold:
pygame.draw.rect(window, BLUE, (target_4.x, target_4.y, target_4.h, target_4.w))
pygame.draw.rect(window, BLUE, (target_hbx4.x, target_hbx4.y, target_hbx4.h,target_hbx4.w), 2)
all_targets_keep.append(target_4)
if clock > target_5_threshold:
pygame.draw.rect(window, BLUE, (target_5.x, target_5.y, target_5.h, target_5.w))
pygame.draw.rect(window, BLUE, (target_hbx5.x, target_hbx5.y, target_hbx5.h,target_hbx5.w), 2)
all_targets_keep.append(target_5)
if clock > target_6_threshold:
pygame.draw.rect(window, BLUE, (target_6.x, target_6.y, target_6.h, target_6.w))
pygame.draw.rect(window, BLUE, (target_hbx6.x, target_hbx6.y, target_hbx6.h,target_hbx6.w), 2)
all_targets_keep.append(target_6)
all_targets = all_targets_keep
#Draw the player sprite. (Module 6)
pygame.draw.rect(window, BLUE, (player_sprite_x, player_sprite_y, player_sprite_w, player_sprite_h))
#Draw each item in all_bullets. (Module 7)
for item in all_bullets:
pygame.draw.rect(window, BLUE, (item['x']-5, item['y']-5, 10, 10))
b_hitbox = (item['x']-10, item['y']-10, 20, 20)
pygame.draw.rect(window, BLUE, b_hitbox, 2)
for item in all_bullets_keep:
if item['y']-30 < (target_hbx1.y) + (target_hbx1.h) and item['y']+30 > target_hbx1.y:
if item['x']+10 > target_hbx1.x and item['x']-30 < (target_hbx1.x) + (target_hbx1.w):
target_1.hit()
pygame.display.update()
#tick_busy_loop limits number of times the game can refresh per second. (Module 8)
py_clock.tick_busy_loop(120)
pygame.quit()
There's a few minor bugs in your code. I think the target is not disappearing because of something going on in with the all_targets_keep list. It looks like the code is re-adding the target to the "keep" list, whether it's been hit or not.
Your approach to using a class to hold all the Target code is a good idea. But all the targeting code is still spread throughout your main loop. This is causing code-clutter and making the task more difficult.
By moving this code into the class, it frees the code from having a "special case" for each of the targets. The target knows everything about itself, and can perform any test inside itself. For example, the drawing of the target to the screen.
class Target:
def __init__(self, x, y, h, w, v, threshold):
self.x = x
self.y = y
self.h = h
self.w = w
self.v = v
self.threshold = threshold
self.hit = False
def draw( self, window ):
# body
pygame.draw.rect( window, BLUE, ( self.x, self.y, self.w, self.h ), 0 )
# hit-box
pygame.draw.rect( window, BLUE, ( self.x-5, self.y-5, self.w+10, self.h+10 ), 1 )
When the target has a member function to draw itself, that whole-whack of drawing code goes away to become a single, simple function. Then the drawing of all targets becomes:
clock += 1
for item in all_targets:
if ( clock > item.threshold and not item.hit ):
item.draw( window )
The target's "hit box" is also just a function of the existing points, there's no need to keep these separate. So, similarly, using a PyGame rect, a target can check whether it has been hit by a bullet:
class Target:
...
def collidesWith( self, bullet_pos ):
# hit-box is 5 pixels offset from target
target_rect = pygame.Rect( self.x-5, self.y-5, self.w+10, self.h+10 )
self.hit = target_rect.collidepoint( bullet_pos )
return self.hit
Anyway, you're making good progress. But what commenter #importrandom says is true - it would really be easier (eventually) for you to use the PyGame built-in sprite classes. They already take care of a lot of the code you're writing yourself. If you don't want to, that's fine, it's your code.
I got a program in Pygame which allows me to show up elements from the list list. Each 3 seconds, it updates and displays the next element from list.
My problem is that elements are overlapping on screen, but I want to update it each time 3 seconds have passed. I already used:
pygame.display.update()
but it does not work.
list = ["x", "y", "z"]
if time > 3 and i < len(list):
font = pygame.font.SysFont("comicsansms", 72)
text2 = font.render(str(list[i]), True, (0, 128, 0))
screen.blit(text2,
(430 - text2.get_width() // 1, 220 - text2.get_height() // 2))
pygame.display.update()
pygame.display.flip()
clock.tick(30)
i = i + 1
Here's something that updates what's displayed every three seconds:
import sys
import time
import pygame
from pygame.locals import *
pygame.init()
FPS = 30
WINDOWWIDTH = 640
WINDOWHEIGHT = 480
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
clock = pygame.time.Clock()
font = pygame.font.SysFont("comicsansms", 72)
screen = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption('Test')
my_list = ["x", "y", "z"]
bkgr = BLACK
i = len(my_list) - 1 # Index of last elememnt (so first is next displayed).
start_time = 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if (time.time() - start_time) > 3: # 3 seconds since last update?
i = (i + 1) % len(my_list)
start_time = time.time()
screen.fill(bkgr)
text2 = font.render(str(my_list[i]), True, (0, 128, 0))
screen.blit(text2, (430 - text2.get_width() // 1,
220 - text2.get_height() // 2))
pygame.display.update()
clock.tick(FPS)
I'm making an arcade game using pygame and I'm trying to have a sprite change positions every few seconds.
I've tried using time.sleep(1) and changing the frame rate to .5 (clock.tick(.5)).
Both worked to make the object change position only after the time interval has passed, however they also make the sprite following my mouse update coordinates at the same rate.
I've been researching and can't seem to find another way to make the sprite move without making my program refresh slower or 'sleep' every time it runs.
You can use an Event for this together with pygame.time.set_timer():
pygame.time.set_timer()
repeatedly create an event on the event queue
set_timer(eventid, milliseconds) -> None
Set an event type to appear on the event queue every given number of milliseconds
Here's a simple, complete example. Note how the enemies move every 1000ms sideways, every 3500ms downwards, and you can shoot every 450ms (all using events).
import pygame
# you'll be able to shoot every 450ms
RELOAD_SPEED = 450
# the foes move every 1000ms sideways and every 3500ms down
MOVE_SIDE = 1000
MOVE_DOWN = 3500
screen = pygame.display.set_mode((300, 200))
clock = pygame.time.Clock()
pygame.display.set_caption("Micro Invader")
# create a bunch of events
move_side_event = pygame.USEREVENT + 1
move_down_event = pygame.USEREVENT + 2
reloaded_event = pygame.USEREVENT + 3
move_left, reloaded = True, True
invaders, colors, shots = [], [] ,[]
for x in range(15, 300, 15):
for y in range(10, 100, 15):
invaders.append(pygame.Rect(x, y, 7, 7))
colors.append(((x * 0.7) % 256, (y * 2.4) % 256))
# set timer for the movement events
pygame.time.set_timer(move_side_event, MOVE_SIDE)
pygame.time.set_timer(move_down_event, MOVE_DOWN)
player = pygame.Rect(150, 180, 10, 7)
while True:
clock.tick(40)
if pygame.event.get(pygame.QUIT): break
for e in pygame.event.get():
if e.type == move_side_event:
for invader in invaders:
invader.move_ip((-10 if move_left else 10, 0))
move_left = not move_left
elif e.type == move_down_event:
for invader in invaders:
invader.move_ip(0, 10)
elif e.type == reloaded_event:
# when the reload timer runs out, reset it
reloaded = True
pygame.time.set_timer(reloaded_event, 0)
for shot in shots[:]:
shot.move_ip((0, -4))
if not screen.get_rect().contains(shot):
shots.remove(shot)
else:
hit = False
for invader in invaders[:]:
if invader.colliderect(shot):
hit = True
i = invaders.index(invader)
del colors[i]
del invaders[i]
if hit:
shots.remove(shot)
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]: player.move_ip((-4, 0))
if pressed[pygame.K_RIGHT]: player.move_ip((4, 0))
if pressed[pygame.K_SPACE]:
if reloaded:
shots.append(player.copy())
reloaded = False
# when shooting, create a timeout of RELOAD_SPEED
pygame.time.set_timer(reloaded_event, RELOAD_SPEED)
player.clamp_ip(screen.get_rect())
screen.fill((0, 0, 0))
for invader, (a, b) in zip(invaders, colors):
pygame.draw.rect(screen, (150, a, b), invader)
for shot in shots:
pygame.draw.rect(screen, (255, 180, 0), shot)
pygame.draw.rect(screen, (180, 180, 180), player)
pygame.display.flip()
How about
var = 0
while True:
event_handling()
game_logic()
if var == 5:
sprite.update.position()
var = 0
pygame.display.flip()
var += 1
Obviously, this is just pseudo code, but you get the idea.
I'm in a game programming class and I'm supposed to make a game where there are sprites falling down from the top of the screen that you catch or avoid. Right now I'm working on making the objects fall and they were working at first but then I screwed something up because now they appear patchily/randomly disappear while falling down the screen. I've tried changing various things in the code to fix the problem but I've been stuck for a while.
I'm pretty certain that I messed something up either blitting the coal/candy to the screen or adding new ones to fall once others disappear, but I included a large portion of my code just in case something there is messing it up. I'll highlight the likely messed up sections below.
Oh, I also always really appreciate comments on how to make code more concise/efficient, but the teacher expects the code to be similar to how he taught us, so if you do have a specific fix, if it could keep the code similar to how it is right now I'd really appreciate it!
Thank you so much!
#Setting up time/timer
time = 6000
TICKTOCK = 0
pygame.time.set_timer (TICKTOCK+1, 10)
#Class for candy
class Candy:
def __init__(self):
self.x = random.randint(0, SCREEN_WIDTH - 150)
self.y = random.randint(-1000, 0)
self.image_Candy = pygame.image.load('konpeito.png')
self.height = self.image_Candy.get_height()
self.width = self.image_Candy.get_width()
def collide (self, sprite):
selfRect = pygame.Rect(self.x, self.y, self.width, self.height)
spriteRect = pygame.Rect(sprite.x, sprite.y, sprite.width, sprite.height)
if selfRect.colliderect(spriteRect):
return True
else:
return False
#class for coal
class Coal:
def __init__(self):
self.x = random.randint(0, SCREEN_WIDTH - 150)
self.y = random.randint(-1000, SCREEN_HEIGHT)
self.image_Coal = pygame.image.load('coal.png')
self.width = self.image_Coal.get_width()
self.height = self.image_Coal.get_height()
def collide (self, sprite):
selfRect = pygame.Rect(self.x, self.y, self.width, self.height)
spriteRect = pygame.Rect(sprite.x, sprite.y, sprite.width, sprite.height)
if selfRect.colliderect(spriteRect):
return True
else:
return False
#class for sootsprite (collects candy and coal)
class Sootsprite:
def __init__(self):
self.x = random.randint(0, SCREEN_WIDTH)
self.y = random.randint(0, SCREEN_HEIGHT)
self.image_bowl = pygame.image.load('sootsprite.png')
self.height = self.image_bowl.get_height()
self.width = self.image_bowl.get_width()
clock = pygame.time.Clock()
fps = 10
#Creating candies and rocks
bowl = []
for i in range (15):
candyInstance = Candy()
bowl.append(candyInstance)
rocks = []
for i in range (8):
coalInstance = Coal()
rocks.append(coalInstance)
catch = Sootsprite()
playground.fill(cyan)
Game_Over = False
while not Game_Over:
font = pygame.font.SysFont(None, 30)
endfont = pygame.font.SysFont(None, 100)
text = font.render('Points: ' + str(total) + '/15', True, black)
playground.blit(text, (0,0))
timer = font.render('Time remaining: ' + str(time), True, black)
playground.blit(timer, (0, 40))
end = endfont.render("GAME OVER." + str(total) + " POINTS EARNED", True, black)
This is where I blitted things onscreen and potentially screwed up:
playground.blit(catch.image_bowl, (catch.x, catch.y))
playground.blit(candyInstance.image_Candy, (candyInstance.x, candyInstance.y))
playground.blit(coalInstance.image_Coal, (coalInstance.x, coalInstance.y))
for event in pygame.event.get():
if event.type == pygame.QUIT:
Game_Over = True
if event.type == TICKTOCK+1:
time -=1
#ends game when time is over
if time < 0:
playground.blit(end, (300, 400))
#moving sootsprite with mouse
if event.type == MOUSEMOTION:
(catch.x, catch.y) = pygame.mouse.get_pos()
#making candy fall
for candyInstance in bowl:
if candyInstance.x <= 0:
candyInstance.x += 0
elif candyInstance.x >= 0 :
candyInstance.x -=0
if candyInstance.y <= (SCREEN_HEIGHT+150):
candyInstance.y += 20
elif candyInstance.y > (SCREEN_HEIGHT + 150) :
candyInstance.y = -100
this is where I'm removing things and adding new ones and might've messed up:
#removing candy when collected
for candyInstance in bowl:
if candyInstance.collide(catch):
bowl.remove(candyInstance)
total += 1
candyInstance = Candy()
bowl.append(candyInstance)
#making coal fall
for coalInstance in rocks:
if coalInstance.x <= 0:
coalInstance.x += 0
elif coalInstance.x >= 0 :
coalInstance.x -=0
if coalInstance.y <= (SCREEN_HEIGHT + 200):
coalInstance.y += 20
elif coalInstance.y > (SCREEN_HEIGHT + 200) :
coalInstance.y = -100
this is also a place where I removed objects and added new ones:
#removing coal when collected
for coalInstance in rocks:
if coalInstance.collide(catch):
rocks.remove(coalInstance)
total -= 1
coalInstance = Coal()
rocks.append(coalInstance)
pygame.display.flip()
playground.fill(cyan)
clock.tick (fps)
pygame.quit()
You blit only one candy. Use for loop to blit all candies.
for candyInstance in bowl:
playground.blit(candyInstance.image_Candy, (candyInstance.x, candyInstance.y))
You have the same problem with rocks
for coalInstance in rocks:
playground.blit(coalInstance.image_Coal, (coalInstance.x, coalInstance.y))