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.
Related
I'm making a game in python with pygame.
I've made a parent class Items with 4 children.
As far as I can see, they should all be moving at speed 'dx' as defined in the Items class.
However, when I run the program, some of them are much faster than others. It seems to be inconsistent as to which colour items are faster, too.
import pygame
from random import randint
WIDTH, HEIGHT = 800, 600
WINDOW = pygame.display.set_mode((WIDTH, HEIGHT))
FPS = 60
clock = pygame.time.Clock()
gravity = 15
# Colours
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
def draw_window():
WINDOW.fill(WHITE)
for item in items:
item.draw_item()
pygame.display.update()
def main():
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
clock.tick(FPS)
for item in items:
item.movement()
draw_window()
pygame.quit()
class Item:
def __init__(self, x):
self.x = x
self.radius = 10
self.y = randint(100, 500) + self.radius
self.dx = -2
def perform_action(self):
pass
def movement(self):
self.x += self.dx
if self.x < 0 - WIDTH:
self.x = WIDTH + randint(0, 300)
self.y = HEIGHT - 40 - randint(0, 400)
def draw_item(self, colour):
pygame.draw.circle(WINDOW, colour, (self.x, self.y), self.radius)
class GravUp(Item):
def __init__(self, x):
super().__init__(x)
def draw_item(self):
super().draw_item(RED)
def perform_action(self):
global gravity
gravity += 3
class GravDown(Item):
def __init__(self, x):
super().__init__(x)
def draw_item(self):
super().draw_item(GREEN)
class AgilUp(Item):
def __init__(self, x):
super().__init__(x)
def draw_item(self):
super().draw_item(CYAN)
item_types = {0: GravUp(WIDTH + randint(0, 500)),
1: GravDown(WIDTH + randint(0, 500)),
2: AgilUp(WIDTH + randint(0, 500))}
items = []
for i in range(10):
items.append(item_types[randint(0, 2)])
if __name__ == '__main__':
main()
When printing out the addresses of the created objects you will see why there are different speeds:
<__main__.AgilUp object at 0x0BD30E70>
<__main__.AgilUp object at 0x0BD30E70>
<__main__.GravUp object at 0x0BD30550>
<__main__.AgilUp object at 0x0BD30E70>
<__main__.GravUp object at 0x0BD30550>
<__main__.GravUp object at 0x0BD30550>
<__main__.GravDown object at 0x0BD306F0>
<__main__.GravDown object at 0x0BD306F0>
<__main__.AgilUp object at 0x0BD30E70>
<__main__.AgilUp object at 0x0BD30E70>
So you don't have 10 objects, but only three, and 10 random pointers to one of these. In this example, the movement instructions will call 5 times AgilUp, 3 times GravUp and 2 times GravDown.
Redesigning the object creation process should fix the issue.
The answer to your random speed behavior resides within the following lines of code in your program.
item_types = {0: GravUp(WIDTH + randint(0, 500)),
1: GravDown(WIDTH + randint(0, 500)),
2: AgilUp(WIDTH + randint(0, 500))}
items = []
for i in range(10):
items.append(item_types[randint(0, 2)])
What this bit of code is doing is indeed defining your three colored circles. Then, it is randomly assigning a number of times (one out of ten ) each circle will be moved and redrawn.
When I added in a print operation within the "draw_item" function and ran the program, I was able to determine the number of drawing/redrawing calls each circle was getting by printing out the item's RGB color value.
(0, 255, 0)
(255, 0, 0)
(255, 0, 0)
(255, 0, 0)
(0, 255, 0)
(0, 255, 0)
(0, 255, 255)
(255, 0, 0)
(0, 255, 0)
(0, 255, 255)
If you tally up the quantities, the green circle gets four calls to move and redraw, the red circle gets four calls to move and redraw, and the cyan circle gets two calls to move and redraw. Running this again would probably produce a different mix of calls for each color.
Reviewing your code, I am guessing what you ultimately wanted to do was create ten circles with random colors at random positions. If that is actually what you want to do, you will need to revise the block of code to derive a random number first, and then based upon that number, populate the item group with the random item type.
I hope that clarifies things.
Regards.
I am trying to draw squares in random positions and random rgb values and I want 1000 of them to be created. The problem I'm facing is that everytime the loop for drawing occurs, it randomizes it all again, is there any way to make this not happen
import pygame
import sys
import random
pygame.init()
win = pygame.display.set_mode((800,600))
pygame.display.set_caption("Simulation")
def safeZone():
#Draws a top rectangle
pygame.draw.rect(win, (50,205,50), (0, 0, 800, 100))
def dot():
width = 10
height = 10
spawnX = random.randrange(1, 801)
spawnY = random.randrange(1, 601)
r = random.randrange(1, 256)
g = random.randrange(1, 256)
b = random.randrange(1, 256)
pygame.draw.rect(win, (r, g, b), (spawnX, spawnY, width, height))
def population(size):
for x in range(size):
dot()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill((255, 255, 255))
safeZone() # Always draw dots after safe zone
population(1000)
pygame.display.update()
pygame.quit()
Create a dot collection, then just draw that dot collection. Now you can update the dot positions separately, and they will redraw in the new positions. Here, I'm having each dot move a random amount in every loop.
import pygame
import sys
import random
pygame.init()
win = pygame.display.set_mode((800,600))
pygame.display.set_caption("Simulation")
class Dot:
def __init__(self):
self.spawnX = random.randrange(0, 800)
self.spawnY = random.randrange(0, 600)
self.r = random.randrange(0, 256)
self.g = random.randrange(0, 256)
self.b = random.randrange(0, 256)
def safeZone():
#Draws a top rectangle
pygame.draw.rect(win, (50,205,50), (0, 0, 800, 100))
def drawdot(dot):
width = 10
height = 10
pygame.draw.rect(win, (dot.r, dot.g, dot.b), (dot.spawnX, dot.spawnY, width, height))
def population(dots):
for dot in dots:
dot.spawnX += random.randrange(-3,4)
dot.spawnY += random.randrange(-3,4)
drawdot(dot)
alldots = [Dot() for _ in range(1000)]
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill((255, 255, 255))
safeZone() # Always draw dots after safe zone
population(alldots)
pygame.display.update()
A worthwhile modification is to store the whole rectangle in the object:
...
class Dot:
def __init__(self):
self.location = [
random.randrange(0, 800),
random.randrange(0, 600),
10, 10
]
self.color = (
random.randrange(0, 256),
random.randrange(0, 256),
random.randrange(0, 256)
)
def move(self, dx, dy ):
self.location[0] += dx
self.location[1] += dy
def drawdot(dot):
pygame.draw.rect(win, dot.color, dot.location)
def population(dots):
for dot in dots:
dot.move( random.randrange(-3,4), random.randrange(-3,4) )
drawdot(dot)
...
You call a function dot() in which you have assigned randomization. You should introduce a piece of code that randomizes the values outside of the dot() function, and store them in a separate array, and then call the function.
Your description sounds like you aren't necessarily trying to store the result so much as you want the process to be the same every time, but still sort of random? You could just use a hard-coded seed?
import random
random.seed(10)
print(random.random())
See this link for more detail: Random Seed
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
In this program when the user types more text I want the rectangle to automatically get longer when the user types to keep the letters inside of the rectangle. However, it doesn't update the rectangle when the text gets longer. How do I fix this?
from pygame import *
init()
screen = display.set_mode((800, 600))
name_font = font.Font(None, 32)
name_text = ''
class Rectangle:
def __init__(self, x, y):
self.x = x
self.y = y
self.input_rect = Rect(x, y, 140, 32)
self.text_surface = name_font.render(name_text, True, (255, 255, 255))
color = Color('lightskyblue3')
draw.rect(screen, color, self.input_rect, 2)
self.input_rect.w = self.text_surface.get_width() + 10
screen.blit(self.text_surface, (self.input_rect.x + 5, self.input_rect.y + 5))
def naming():
global name_text
if events.type == KEYDOWN:
if keys[K_BACKSPACE]:
name_text = name_text[:-1]
screen.fill((0, 0, 0))
rect_1 = Rectangle(200, 200)
else:
name_text += events.unicode
while True:
rect_1 = Rectangle(200, 200)
for events in event.get():
keys = key.get_pressed()
naming()
if events.type == QUIT:
quit()
display.update()
time.delay(1)
The Rectangle.text_surface is a PyGame Surface. So you can easily get the precise width of the bounding box by simply calling self.text_surface.get_width().
But you start the size of the border-rect at 140, so this size has to be the maximum of 140 or whatever the new (longer) width is. Another problem is that when the rectangle re-sizes, the old rectangle is left behind. So whenever we now re-draw the rectangle, it erases the background to black.
This is all pretty easily encapsulated into the exiting __init__():
def __init__(self, x, y):
self.x = x
self.y = y
self.text_surface = name_font.render(name_text, True, (255, 255, 255))
rect_width = max( 140, 10 + self.text_surface.get_width() ) # Adjust the width
color = Color('lightskyblue3')
self.input_rect = Rect(x, y, rect_width, 32) # Use new width (if any)
draw.rect(screen, (0,0,0) , self.input_rect, 0) # Erase any existing rect
draw.rect(screen, color, self.input_rect, 2)
self.input_rect.w = self.text_surface.get_width() + 10
screen.blit(self.text_surface, (self.input_rect.x + 5, self.input_rect.y + 5))
I'm trying to build a game that involves a small red player (rectangle) being controlled by the arrow keys. There's a white grid on top of a black background, 2 different coloured objectives (rectangles), and several random red boxes (rectangles).
The part I need help with is moving the small red player. I can move it, but it seems to draw itself in the new position, but the version of the rectangle that was previously drawn stays there, forming a line. I want the entire rectangle to move and not leave any traces/previous versions of itself.
According to some other posts, I've heard that the only way to do this is to fill the screen with the background colour (in this case, black) and redraw the players on top of it; however, this is really hard in my case as I have the red boxes and objectives placed randomly, so every time I draw them again, they draw in a new random position, not in their old positions. I want the red boxes and objectives to stay in the same position, but have the player rectangles move around (while basically deleting the older versions of themselves).
Here's the code I currently have (I've excluded the basics, like defining colours, imports, and setting the screen height/width):
p1_velocity_x = 0
p1_velocity_y = 0
p2_velocity_x = 0
p2_velocity_y = 0
def grid():
for col in range(0, screen_width - 100, 10):
for row in range(0, screen_height, 10):
screen.set_at((row, col), white)
def red_boxes():
for i in range(100):
pygame.draw.rect(screen, red, (randrange(1, 1200), randrange(1, 470), 25, 25))
def blue_score_box():
pygame.draw.rect(screen, blue, (randrange(1, 1200), randrange(1, 470), 25, 25))
def yellow_score_box():
pygame.draw.rect(screen, yellow, (randrange(1, 1200), randrange(1, 470), 25, 25))
class Player:
color = (0, 0, 0)
def __init__(self, color):
self.x = 0
self.y = 0
self.color = color
p1 = Player(red)
p1.x = randrange(1, 1200, 10)
p1.y = randrange(1, 470, 10)
p2 = Player(yellow)
p2.x = randrange(1, 1200, 10)
p2.y = randrange(1, 470, 10)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
p1.x += 10
screen.fill(black)
grid()
red_boxes()
yellow_score_box()
blue_score_box()
pygame.draw.rect(screen, p1.color, (p1.x, p1.y, 10, 10))
pygame.draw.rect(screen, p2.color, (p2.x, p2.y, 10, 10))
pygame.display.update()
The result of the above code is that I'm able to move the red rectangle as I want, but since I call the red_boxes(), yellow_score_box(), and blue_score_box() methods in the while loop, they keep calling indefinitely, drawing random red, blue, and yellow rectangles all over the screen almost every second. I want them to stay in one place while having the functionality of moving the player as I do now.
Create a pygame.Surface with the same size than the red player rectangle:
bkP1 = pygame.Surface((10, 10))
Backup the background (pygame.Surface.blit) and store the position of the player, before the player is drawn:
prevPos = (p1.x, p1.y)
bkP1.blit(screen, (0, 0), (*prevPos, 10, 10))
Draw the background on top of the player when the player has to be erased:
screen.blit(bkP1, prevPos)
The process may work as follows:
prevPos = None
bkP1 = pygame.Surface((10, 10))
while True:
# [...]
if prevPos:
screen.blit(bkP1, prevPos)
prevPos = (p1.x, p1.y)
bkP1.blit(screen, (0, 0), (*prevPos, 10, 10))
pygame.draw.rect(screen, p1.color, (p1.x, p1.y, 10, 10))
Of course it is possible to add the mechanism to the class Player:
class Player:
def __init__(self, color):
self.x = 0
self.y = 0
self.color = color
self. prevPos = None
self.bk = pygame.Surface((10, 10))
def draw(self, screen):
if self.prevPos:
screen.blit(self.bk, self.prevPos)
self.prevPos = (self.x, self.y)
self.bk.blit(screen, (0, 0), (*self.prevPos, 10, 10))
pygame.draw.rect(screen, self.color, (self.x, self.y, 10, 10))
while True:
# [...]
p1.draw(screen)