Pygame - center text in moving rect - python

i'm making a drop the number game in pygame and have squares which will fall from the top of the screen to the bottom whilst having a numeric value and that number being displayed on each square. I am struggling to get the text centered on the square and it is made more difficult when each number can have 1 - 4 digits in them
class sqr:
def __init__(self):
self.colours = [(255,0,0), (0,255,0), (0,0,255)]
self.Xpositions = [0,125,245,365,485]
self.OnScreen = False
self.X = random.choice(self.Xpositions)
self.Y = 0
self.colour = random.choice(self.colours)
self.number = random.choice([20,40,80])
self.numberFont = pygame.font.Font("TitilliumWeb-Black.ttf", 48)
def drawSquare(self, colour, X, Y):
pygame.draw.rect(screen, colour, (X, Y, 120, 120))
def numberTextFunc(self, X, Y):
numberText = self.numberFont.render(f"{self.number}", True, (87, 63, 63))
screen.blit(numberText, (X , Y))
square = sqr()
gameRunning = True
while gameRunning:
background = screen.fill((0,100,140))
#draw lines for grid
for i in range(5):
pygame.draw.rect(screen, (0,0,0), (line[i], 0 , 5, 800))
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameRunning = False
#square
square.drawSquare(square.colour, square.X, square.Y)
square.numberTextFunc(square.X, square.Y)
square.OnScreen = True
square.Y += 1
if square.Y >= 680:
square.Y = 680
pygame.display.update()

Use pygame.Surface.get_rect to get the text rectangle. pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0). Set the center of the text rectangle by the center of the square. Use the text reectnagle to blit the text. The second argument of blit is either a tuple (x, y) or a rectangle. With a rectangle, only the upper left corner of the rectangle is taken into account. Therefore you can pass the text rectangle directly to blit:
class sqr:
# [...]
def numberTextFunc(self, X, Y):
numberText = self.numberFont.render(f"{self.number}", True, (87, 63, 63))
rect = pygame.Rect(X, Y, 120, 120)
textRect = numberText.get_rect(center = rect.center)
screen.blit(numberText, textRect)
You can simplify your code. X and Y are attributes of the object. Hence there is no need to pass the coordinates to the methods:
class sqr:
def __init__(self):
self.colours = [(255,0,0), (0,255,0), (0,0,255)]
self.Xpositions = [0,125,245,365,485]
self.OnScreen = False
self.X = random.choice(self.Xpositions)
self.Y = 0
self.colour = random.choice(self.colours)
self.number = random.choice([20,40,80])
self.numberFont = pygame.font.Font("TitilliumWeb-Black.ttf", 48)
def drawSquare(self, colour):
pygame.draw.rect(screen, colour, (self.X, self.Y, 120, 120))
def numberTextFunc(self):
numberText = self.numberFont.render(f"{self.number}", True, (87, 63, 63))
rect = pygame.Rect(self.X, self.Y, 120, 120)
textRect = numberText.get_rect(center = rect.center)
screen.blit(numberText, textRect)
gameRunning = True
while gameRunning:
background = screen.fill((0,100,140))
#draw lines for grid
for i in range(5):
pygame.draw.rect(screen, (0,0,0), (line[i], 0 , 5, 800))
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameRunning = False
#square
square.drawSquare(square.colour)
square.numberTextFunc()
square.OnScreen = True
square.Y += 1
if square.Y >= 680:
square.Y = 680
pygame.display.update()

Related

How to create button with transparent background and shadow in pygame?

Hi guys I need your help. I want to set opacity of the rectangles which are created by help of class button
this is before class
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Snake')
bg_img = pygame.image.load('Graphics/bg.png')
bg_img = pygame.transform.scale(bg_img,(screen_width,screen_height))
class button():
def __init__(self,text,width,height,pos,elevation,image,color,shadow,hover):
self.image = pygame.transform.scale(image, (int(width), int(height)))
self.rect = self.image.get_rect()
self.elevation = elevation
self.dynamic_elecation = elevation
self.original_y_pos = pos[1]
self.color = color
self.color_shadow = shadow
self.hover = hover
self.clicked = False
self.top_rect = pygame.Rect(pos,(width,height))
self.top_color = color
self.bottom_rect = pygame.Rect(pos,(width,height))
self.bottom_color = shadow
font = pygame.font.SysFont('rockwell', 50)
self.text_surf = font.render(text,True,'#FFFFFF')
self.text_rect = self.text_surf.get_rect(center = self.top_rect.center)
here is function for drawing and also detecting if it was clicked
def draw_button(self):
action = False
pos = pygame.mouse.get_pos()
self.top_rect.y = self.original_y_pos - self.dynamic_elecation
self.text_rect.center = self.top_rect.center
self.bottom_rect.midtop = self.top_rect.midtop
self.bottom_rect.height = self.top_rect.height + self.dynamic_elecation
pygame.draw.rect(screen,self.bottom_color, self.bottom_rect, border_radius = 12)
pygame.draw.rect(screen,self.top_color, self.top_rect, border_radius = 12)
screen.blit(self.text_surf, self.text_rect)
if self.top_rect.collidepoint(pos):
self.top_color = self.color
if pygame.mouse.get_pressed()[0]:
self.dynamic_elecation = 0
self.clicked = True
elif pygame.mouse.get_pressed()[0] == 0 and self.clicked == True:
self.clicked = False
action = True
else:
self.dynamic_elecation = self.elevation
self.top_color = self.hover
else:
# self.dynamic_elecation = self.elevation
self.top_color = self.color
screen.blit(self.image, (self.top_rect))
return action
Here I am creating that button
button_snake = '#191919'
hover_snake = '#999999'
shadow_snake = '#191919'
black = button('',600,180,(100,30),0, menu_img, '#191919', '#191919', '#191919')
red_snake = pygame.image.load('Graphics/snake_r.png').convert_alpha()
In conclusion I want to have another value in class button (self,text,...) and there would be opacity
example
See How to separately change the opacity of a text on a button pygame? and Draw a transparent rectangles and polygons in pygame. You have to draw the rectangle on a transparent pygame.Surface and blit the Surface on the screen. A transparent pygame.Surface can be crate with the pygame.SRCALPHA flag:
top_surf = pygame.Surface(self.top_rect.size, pygame.SRCALPHA)
pygame.draw.rect(top_surf, self.top_color, (0, 0, *self.top_rect.size), border_radius = 12)
screen.blit(top_surf, self.top_rect.topleft)
Coplete example:
import pygame
class Button():
def __init__(self, text, width, height, pos, elevation, image, color, shadow, hover):
#self.image = pygame.transform.scale(image, (int(width), int(height)))
#self.rect = self.image.get_rect()
self.elevation = elevation
self.original_y_pos = pos[1]
self.color = color
self.color_shadow = shadow
self.hover = hover
self.clicked = False
self.top_rect = pygame.Rect(pos,(width,height))
self.top_color = color
self.bottom_rect = pygame.Rect(pos,(width,height))
self.bottom_color = shadow
font = pygame.font.SysFont('rockwell', 50)
self.text_surf = font.render(text,True,'#FFFFFF')
self.text_rect = self.text_surf.get_rect(center = self.top_rect.center)
def draw_button(self, screen):
action = False
pos = pygame.mouse.get_pos()
top_rect = self.top_rect.copy()
bottom_rect = self.bottom_rect.copy()
bottom_rect.x += 20
bottom_rect.y += 20
if top_rect.collidepoint(pos):
self.top_color = self.color
if pygame.mouse.get_pressed()[0]:
self.clicked = True
bottom_rect.inflate_ip(self.elevation, self.elevation)
top_rect.inflate_ip(self.elevation, self.elevation)
elif pygame.mouse.get_pressed()[0] == 0 and self.clicked == True:
self.clicked = False
action = True
self.top_color = self.hover
else:
self.top_color = self.color
bottom_surf = pygame.Surface(bottom_rect.size, pygame.SRCALPHA)
pygame.draw.rect(bottom_surf, self.bottom_color, (0, 0, *bottom_rect.size), border_radius = 12)
screen.blit(bottom_surf, bottom_rect.topleft)
top_surf = pygame.Surface(top_rect.size, pygame.SRCALPHA)
pygame.draw.rect(top_surf, self.top_color, (0, 0, *top_rect.size), border_radius = 12)
screen.blit(top_surf, top_rect.topleft)
screen.blit(self.text_surf, self.text_rect)
return action
pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
background = pygame.Surface(screen.get_size())
ts, w, h, c1, c2 = 50, *screen.get_size(), (128, 128, 128), (96, 96, 96)
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)
font = pygame.font.SysFont(None, 80)
text = font.render("Button", True, (255, 255, 255))
button = Button("Button", 200, 70, (80, 120), 20, None, (128, 128, 255, 128), (64, 64, 128, 128), (255, 128, 255, 128))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.blit(background, (0, 0))
button.draw_button(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
exit()

How do i make a text box pop up when i hover over a button in pygame

I want to be able to make a text box pop up when I hover over the button, now the button changes colour when I hover over it but no text box is displayed how can I make that happen or do I need to something entirely different like make a new screen pop up when I hover over either way the hovering over isnt working and I dont know how to fix it and id like some help please
import pygame
pygame.init()
pygame.font.init()
colours = {"White" : (255, 255, 255), "Black" : (0, 0, 0), "Red" : (255, 0, 0), "Blue" : (0, 0, 255), "Green" : (0, 255, 0)}
orighpamt = 10
currentGoblin = 0
class Screen():
def __init__(self, title, width=400, height=600, fill=colours["White"]):
self.title=title
self.width=width
self.height = height
self.fill = fill
self.current = False
def makeCurrent(self):
pygame.display.set_caption(self.title)
self.current = True
self.screen = pygame.display.set_mode((self.width, self.height))
def endCurrent(self):
self.current = False
def checkUpdate(self):
return self.current
def screenUpdate(self):
if(self.current):
self.screen.fill(self.fill)
def returnTitle(self):
return self.screen
class Button():
def __init__(self, x, y, sx, sy, bcolour, fbcolour, font, fontsize, fcolour, text):
self.x = x
self.y = y
self.sx = sx
self.sy = sy
self.bcolour = bcolour
self.fbcolour = fbcolour
self.fcolour = fcolour
self.fontsize = fontsize
self.text = text
self.current = False
self.buttonf = pygame.font.SysFont(font, fontsize)
def showButton(self, display):
if(self.current):
pygame.draw.rect(display, self.fbcolour, (self.x, self.y, self.sx, self.sy))
else:
pygame.draw.rect(display, self.bcolour, (self.x, self.y, self.sx, self.sy))
textsurface = self.buttonf.render(self.text, False, self.fcolour)
display.blit(textsurface, ((self.x + (self.sx/2) - (self.fontsize/2)*(len(self.text)/2) - 5,(self.y + (self.sy/2) -(self.fontsize/2) - 4))))
def focusCheck(self, mousepos, mouseclick):
if(mousepos[0] >= self.x and mousepos[0] <= self.x + self.sx and mousepos[1] >= self.y and mousepos[1] <= self.y + self.sy):
self.current = True
return mouseclick
else:
self.current = False
return False
class Enemy(pygame.sprite.Sprite):
def __init__(self, dx, dy, filename):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(filename).convert()
self.rect = self.image.get_rect()
self.rect.x = dx
self.rect.y = dy
def draw(self, screen):
screen.blit(self.image, self.rect)
menuScreen = Screen("Menu Screen")
screen2 = Screen("Screen 2")
win = menuScreen.makeCurrent()
done = False
font = pygame.font.Font('freesansbold.ttf', 32)
clickdamage = 1
hitpoints = orighpamt
DungeonButton = Button(125, 500, 150, 50, colours["Black"], colours["Blue"], "arial", 20, colours["White"], "Dungeon")
hitboxButton = Button(80, 50, 280, 400, colours["White"], colours["Red"], "arial", 20, colours["White"], "")
hitpointamount = Button(100, 0, 200, 50, colours["White"], colours["Black"], "arial", 20, colours["Black"], str(hitpoints))
shopButton = Button(125, 500, 150, 50, colours["Black"], colours["Red"], "arial", 20, colours["White"], "Shop")
Assassin_Text = Button(50, 350, 400, 50, colours["White"], colours["Red"], "arial", 18, colours["Black"], "Sharpen your sword and increase your damage per click")
Assassin_Upgrade = Button(10, 300, 125, 50, colours["Black"], colours["Red"], "arial", 15, colours["White"], "Assassin")
goblin = Enemy(0 , 20, "images\goblin-resized.png")
goblin2 = Enemy(80 , 50, "images\monster2.png")
toggle = False
while not done:
menuScreen.screenUpdate()
screen2.screenUpdate()
mouse_pos = pygame.mouse.get_pos()
keys = pygame.key.get_pressed()
mouse_click = False
for event in pygame.event.get():
if(event.type == pygame.QUIT):
done = True
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_click = True
if menuScreen.checkUpdate():
screen2button = shopButton.focusCheck(mouse_pos, mouse_click)
shopButton.showButton(menuScreen.returnTitle())
if screen2button:
win = screen2.makeCurrent()
menuScreen.endCurrent()
elif screen2.checkUpdate():
returnm = DungeonButton.focusCheck(mouse_pos, mouse_click)
Assassin_Upgrade.showButton(screen2.returnTitle())
if Assassin_Upgrade.focusCheck(mouse_pos):
Assassin_Text.showButton(screen2.returnTitle())
if returnm:
win = menuScreen.makeCurrent()
screen2.endCurrent()
pygame.display.update()
pygame.quit()
Add another text attribute to your class. e.g.:
class Button():
def __init__(self, x, y, sx, sy, bcolour, fbcolour, font, fontsize, fcolour, text, tiptext):
# [...]
self.tiptextsurface = self.buttonf.render(tiptext, False, (0, 0, 0), (255, 255, 0))
Draw the text in a Button.showTip method, when self.current is set:
class Button():
# [...]
def showTip(self, display):
if self.current:
mouse_pos = pygame.mouse.get_pos()
display.blit(self.tiptextsurface, (mouse_pos[0]+16, mouse_pos[1]))
Minimal example based on your code:
I've simplified the Button class, but it behaves exactly like the class in your question.
import pygame
pygame.init()
class Button():
def __init__(self, x, y, sx, sy, bcolour, fbcolour, font, fontsize, fcolour, text, tiptext):
self.rect = pygame.Rect(x, y, sx, sy)
self.bcolour = bcolour
self.fbcolour = fbcolour
self.fcolour = fcolour
self.fontsize = fontsize
self.current = False
self.buttonf = pygame.font.SysFont(font, fontsize)
self.textsurface = self.buttonf.render(text, False, self.fcolour)
self.tiptextsurface = self.buttonf.render(tiptext, False, (0, 0, 0), (255, 255, 0))
def showButton(self, display):
color = self.fbcolour if self.current else self.bcolour
pygame.draw.rect(display, color, self.rect)
display.blit(self.textsurface, self.textsurface.get_rect(center = self.rect.center))
def showTip(self, display):
if self.current:
mouse_pos = pygame.mouse.get_pos()
display.blit(self.tiptextsurface, (mouse_pos[0]+16, mouse_pos[1]))
def focusCheck(self, mousepos, mouseclick):
self.current = self.rect.collidepoint(mousepos)
return mouseclick if self.current else True
screen = pygame.display.set_mode((400, 600))
clock = pygame.time.Clock()
font = pygame.font.Font('freesansbold.ttf', 32)
shopButton = Button(125, 500, 150, 50, "black", "red", "arial", 20, "white", "Shop", "Enter the shop")
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if(event.type == pygame.QUIT):
done = True
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_click = True
mouse_pos = pygame.mouse.get_pos()
mouse_click = False
screen2button = shopButton.focusCheck(mouse_pos, mouse_click)
screen.fill((255, 255, 255))
shopButton.showButton(screen)
shopButton.showTip(screen)
pygame.display.update()
pygame.quit()
It's easier to answer questions that provide a Minimal, Reproducible Example. Your sample has external image and font dependencies that will need to be overcome for someone to debug your code.
If your button had an associated rect, then you can tell if the mouse cursor is hovering over the button using Rect.colliderect(position).
Here's a minimal example showing some hovering text when the mouse is in a rectangle:
import pygame
WIDTH = 640
HEIGHT = 480
FPS = 30
pygame.init()
pygame.font.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(f"Color: purple")
clock = pygame.time.Clock()
# create a centered rectangle to fill with color
color_rect = pygame.Rect(100, 100, WIDTH - 200, HEIGHT - 200)
font = pygame.font.SysFont(None, 50, True)
msg = " Hovering Message "
hover_surface = font.render(msg, True, pygame.Color("chartreuse"), pygame.Color("firebrick"))
hovering = False
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# update game elements - Get mouse position
mx, my = pygame.mouse.get_pos()
# draw surface - fill background
screen.fill(pygame.Color("grey"))
## draw central rect
screen.fill(pygame.Color("purple"), color_rect)
## display the hovering text if the mouse is over the rectangle
if color_rect.collidepoint((mx, my)):
screen.blit(hover_surface, (mx, my))
# show surface
pygame.display.update()
# limit frames
clock.tick(FPS)
pygame.quit()
Your code uses a lot of raw geometric calculations where it might be easier in the long run to use some of the native pygame elements. For example, you can make your buttons immobile Sprites, and keep them in a Sprite group to make managing them easier.

How do my make my image (rect) collide with my randomly generated circles to end the game? [duplicate]

This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Closed 1 year ago.
I have been trying to solve this for weeks. This is a free-falling pit game, where my character (in this case a chimpanzee png) falls from the top of the screen to the bottom while trying to dodge the random black circles. I have tried so many angles at tackling this, I have tried the standard collision I was taught (pygame.sprite.groupcollide(Group1, Group2, False, True, collided = None) I have tried doing collisions with the colour black only, different formats of spawning my image and all that, and I haven't been able to find anything that works with my code. It has resulted in the code being very messy. I have tried to clean it up for this, but it still might be hard to understand, but if anybody has any solution to this, please let me know as I have been stumped for weeks. I just want the game to close or "game over" when they touch a circle Code:
import sys
import pygame as pg
RED = (255, 0, 0)
GRAY = (150, 150, 150)
GREEN =(34, 177, 76)
BLACK = (0,0,0)
import random
import math
def main():
width, height = 1024, 768
hbox, vbox = 80, 80
w, h = 640, 240
screen = pg.display.set_mode((width, height))
BG = pg.image.load('jungle.jpg').convert()
img = pg.image.load('MONKEY.png')
img = pg.transform.scale(img, (80, 80))
img.convert()
rect = img.get_rect()
rect.center = w//2, h//2
clock = pg.time.Clock()
Score = 0
class Circle(pg.sprite.Sprite):
def __init__(self):
#You can initialise the size to a random value
self.pos = [random.randint(0, 1000), random.randint(180, 600)]
self.color = (0,0, 0)
self.radius = 20
def draw(self):
pg.draw.circle(screen, self.color, (self.pos[0], self.pos[1]), self.radius)
circles = []
for i in range(20):
circles.append(Circle())
def checkIntersection(c1, c2):
dx = c1.pos[0] - c2.pos[0]
dy = c1.pos[1] - c2.pos[1]
d = math.hypot(dx, dy)
if d < c1.radius + c2.radius:
return True
return False
for i in range(19):
while checkIntersection(circles[i], circles[i + 1]):
circles[i].pos = random.randint(0, 1000), random.randint(0, 700)
velocity = (0, 0)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
# booster
move = 8 if keys[pg.K_LSHIFT] else 4
if keys[pg.K_a]: #to move left
rect.x -= move
if rect.x < 0 : rect.x = 0
if keys[pg.K_d]: #to move right
rect.x += move
if rect.x > width-hbox : rect.x = width - hbox
Score += 1
rect.y += 2.5
screen.blit(BG, (0,0))
screen.blit(img,rect)
pg.draw.rect(screen, RED, rect, 1)
pg.draw.polygon(screen, GREEN, ((1024,768), (0,768), (0,640),(1024,640)))
font = pg.font.SysFont("comicsansms", 45)
text = font.render (" " + str(Score), 1, BLACK)
screen.blit(text,(480,700))
pg.event.get()
for circle in circles:
circle.draw()
pg.display.update()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()
Use a "mask" collision. See How can I made a collision mask? and Pygame mask collision
Create a Sprite class with mask (see pygame.mask.from_surface):
class Circle(pg.sprite.Sprite):
def __init__(self):
super().__init__()
self.pos = [random.randint(0, 1000), random.randint(180, 600)]
self.color = (0,0, 0)
self.radius = 20
self.image = pg.Surface((self.radius*2, self.radius*2), pg.SRCALPHA)
pg.draw.circle(self.image , self.color, (self.radius, self.radius), self.radius)
self.rect = self.image.get_rect(center = self.pos)
self.mask = pg.mask.from_surface(self.image)
Create a Sprite class for the player (also with a mask)
class Player(pg.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pg.image.load('MONKEY.png')
self.image = pg.transform.scale(self.image, (80, 80)).convert()
self.rect = self.image.get_rect()
self.rect.center = x, y
self.mask = pg.mask.from_surface(self.image)
Manage the Sprites in pygame.sprite.Group and use pygame.sprite.spritecollide and pygame.sprite.collide_mask() for the collision test:
if pg.sprite.spritecollide(player, circles, False, collided = pg.sprite.collide_mask):
done = True
Complete example:
import sys
import pygame as pg
RED = (255, 0, 0)
GRAY = (150, 150, 150)
GREEN =(34, 177, 76)
BLACK = (0,0,0)
import random
import math
class Circle(pg.sprite.Sprite):
def __init__(self):
super().__init__()
self.pos = [random.randint(0, 1000), random.randint(180, 600)]
self.color = (0,0, 0)
self.radius = 20
self.image = pg.Surface((self.radius*2, self.radius*2), pg.SRCALPHA)
pg.draw.circle(self.image , self.color, (self.radius, self.radius), self.radius)
self.rect = self.image.get_rect(center = self.pos)
self.mask = pg.mask.from_surface(self.image)
class Player(pg.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pg.image.load('MONKEY.png')
self.image = pg.transform.scale(self.image, (80, 80)).convert()
self.rect = self.image.get_rect()
self.rect.center = x, y
self.mask = pg.mask.from_surface(self.image)
def main():
width, height = 1024, 768
hbox, vbox = 80, 80
w, h = 640, 240
screen = pg.display.set_mode((width, height))
BG = pg.image.load('jungle.jpg').convert()
clock = pg.time.Clock()
Score = 0
player = Player(w//2, h//2)
all_sprites = pg.sprite.Group(player)
circles = pg.sprite.Group()
for i in range(20):
circle = Circle()
circles.add(circle)
all_sprites.add(circle)
def checkIntersection(c1, c2):
dx = c1.pos[0] - c2.pos[0]
dy = c1.pos[1] - c2.pos[1]
d = math.hypot(dx, dy)
if d < c1.radius + c2.radius:
return True
return False
c = circles.sprites()
for i in range(19):
while checkIntersection(c[i], c[i + 1]):
c[i].pos = random.randint(0, 1000), random.randint(0, 700)
velocity = (0, 0)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
# booster
move = 8 if keys[pg.K_LSHIFT] else 4
if keys[pg.K_a]: #to move left
player.rect.x -= move
if player.rect.x < 0 : player.rect.x = 0
if keys[pg.K_d]: #to move right
player.rect.x += move
if player.rect.x > width-hbox : player.rect.x = width - hbox
Score += 1
player.rect.y += 2.5
screen.blit(BG, (0,0))
pg.draw.rect(screen, RED, player.rect, 1)
pg.draw.polygon(screen, GREEN, ((1024,768), (0,768), (0,640),(1024,640)))
font = pg.font.SysFont("comicsansms", 45)
text = font.render (" " + str(Score), 1, BLACK)
screen.blit(text,(480,700))
pg.event.get()
all_sprites.draw(screen)
pg.display.update()
clock.tick(30)
if pg.sprite.spritecollide(player, circles, False, collided = pg.sprite.collide_mask):
done = True
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()

PyGame Snake decreases speed as window size increases

I've been struggling with this problem for a couple days now. I want to eventually convert my snake game script to an exe, for my friends to play. I want the snake to move at the same speed, no matter the size of the window.
For Example: My window size right now is (400, 400). If I increase the size to (800, 800), the snake will move slower. However, the snake velocity is constant at 20 pixels. It almost seems like my main game loop is looping at a slower pace as the window size increases.
I know that the snake has more pixels to traverse with a bigger window size, but how does that affect the snake velocity at all? I'm thinking the solution lies somewhere in the speed at which I'm drawing the snake to the screen, but can't be sure.
import pygame
import sys
import random
import math
import time
pygame.display.set_caption('Snake')
pygame.font.init()
game_running = True
width = 400
height = 400
size = (width, height)
window = pygame.display.set_mode(size) # our surface type
pygame.display.set_caption("Snake Game by Nick Rinaldi")
class Food:
def __init__(self, block_size, surface, x_loc, y_loc): # pass in color and random_x/random_y. block size is a constant
self.block_size = block_size
self.surface = surface # green
self.x_loc = x_loc
self.y_loc = y_loc
self.mask = pygame.mask.from_surface(self.surface)
def draw(self, window):
window.blit(self.surface, (self.x_loc, self.y_loc))
class Snake:
def __init__(self, block_size, surface, x_loc, y_loc):
self.block_size = block_size
self.surface = surface # red
self.x_loc = x_loc
self.y_loc = y_loc
self.body = []
self.direction = None
self.velocity = 20
self.mask = pygame.mask.from_surface(self.surface)
def draw(self, color, window, block_size):
self.seg = []
self.head = pygame.Rect(self.x_loc, self.y_loc, block_size, block_size)
pygame.draw.rect(window, color, self.head)
if len(self.body) > 0:
for unit in self.body:
segment = pygame.Rect(unit[0], unit[1], block_size, block_size)
pygame.draw.rect(window, color, segment)
self.seg.append(segment)
def add_unit(self):
if len(self.body) != 0:
index = len(self.body) - 1
x = self.body[index][0]
y = self.body[index][1]
self.body.append([x, y])
else:
self.body.append([1000, 1000])
def move(self, step):
for index in range(len(self.body) -1, 0, -1):
x = self.body[index-1][0]
y = self.body[index-1][1]
self.body[index] = [x, y]
if len(self.body) > 0:
self.body[0] = [self.x_loc, self.y_loc]
if self.direction == "right": # if specific constant, keep moving in direction
self.x_loc += self.velocity * step
if self.direction == "left":
self.x_loc -= self.velocity * step
if self.direction == "down":
self.y_loc += self.velocity * step
if self.direction == "up":
self.y_loc -= self.velocity * step
def collision(self, obj):
return collide(food)
def gameOver(snake):
white = pygame.Color(255, 255, 255)
display = True
while display:
window.fill(white)
score_font = pygame.font.SysFont("Courier New", 16)
score_label = score_font.render("Your score was: " + str(len(snake.body) + 1), 1, (0, 0, 0))
replay_label = score_font.render("To replay, click the mouse button", 1, (0, 0, 0))
window.blit(score_label, (50, 100))
window.blit(replay_label, (50, 130))
pygame.display.update()
for event in pygame.event.get(): # if we hit "x" to close out the game, close out the game.
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.MOUSEBUTTONDOWN:
main()
pygame.quit()
sys.exit()
clock = pygame.time.Clock()
def main():
game_over = False
x = 20 # x position
y = 20 # y position
block_snakes = []
pygame.init()
clock = pygame.time.Clock()
red = pygame.Color(255, 0, 0)
green = pygame.Color(0, 255, 0)
white = pygame.Color(255, 255, 255)
black = pygame.Color(0, 0, 0)
block_size = 20
randx_green = random.randrange(0, width, 20)
randy_green = random.randrange(0, height, 20)
randx_red = random.randrange(0, width, 20)
randy_red = random.randrange(0, height, 20)
red_square = pygame.Surface((block_size, block_size))
red_square.fill(red)
green_square = pygame.Surface((block_size, block_size))
green_square.fill(green)
snake = Snake(block_size, red_square, 20, 20) # create snake instance
food = Food(block_size, green_square, randx_green, randy_green) # create food instance
def redraw_window():
draw_grid(window, height, width, white)
while game_running:
dt = clock.tick(30) # time passed between each call
step = dt/1000
print(step)
FPS = 60
window.fill(black)
food.draw(window)
snake.draw(red, window, block_size)
redraw_window()
for event in pygame.event.get(): # if we hit "x" to close out the game, close out the game.
if event.type == pygame.QUIT:
pygame.quit()
exit()
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT]: # sets direction attribute as a constant
snake.direction = "right"
if keys[pygame.K_LEFT]:
snake.direction = "left"
if keys[pygame.K_DOWN]:
snake.direction = "down"
if keys[pygame.K_UP]:
snake.direction = "up"
snake.move(step)
collision = collide_food(snake.x_loc, snake.y_loc, food.x_loc, food.y_loc)
if collision:
ac_rand_x = random.randrange(0, width, 20) # after collision, random x
ac_rand_y = random.randrange(0, height, 20) # after collision, random y
# check snake.direction.
food = Food(block_size, green_square, ac_rand_x, ac_rand_y)
food.draw(window)
snake.add_unit()
wall_collide = collide_wall(snake.x_loc, snake.y_loc)
if wall_collide:
gameOver(snake)
# break
for block in snake.body:
if snake.x_loc == block[0] and snake.y_loc == block[1]:
gameOver(snake)
pygame.display.update()
# clock.tick(FPS)
def collide_food(snake_x, snake_y, obj_x, obj_y):
distance = math.sqrt((math.pow(snake_x - obj_x, 2)) + (math.pow(snake_y - obj_y, 2)))
if distance < 20:
return True
else:
return False
def collide_wall(snake_x, snake_y):
if snake_x > width:
game_over = True
return game_over
if snake_y > height:
game_over = True
return game_over
if snake_x < 0:
game_over = True
return game_over
if snake_y < 0:
game_over = True
return game_over
def collide_self(snake_x, snake_y, body_x, body_y):
if (snake_x and snake_y) == (body_x and body_y):
return True
else:
return False
def draw_grid(window, height, width, color):
x = 0
y = 0
grid_blocks = 20
for i in range(height):
x += 20
pygame.draw.line(window, color, (x, 0), (x, height), 1)
for j in range(width):
y += 20
pygame.draw.line(window, color, (0, y), (height, y), 1)
# pygame.display.update()
def display_score():
score_font = pygame.font.SysFont()
def main_menu(width, height):
clock = pygame.time.Clock()
FPS = 60
width = width
height = height
run = True
title_font = pygame.font.SysFont("Courier New", 16)
title_font.set_bold(True)
white = pygame.Color(255, 255, 255)
while run:
window.fill(white)
title_label = title_font.render("Snake Game by Nick Rinaldi ", 1, (0, 0, 0))
sponser_label = title_font.render("Sponsored by #goodproblemsnyc", 1, (0, 0, 0))
window.blit(title_label, ((width/4, height/4)))
window.blit(sponser_label, ((width/4, height/4 + 30)))
pygame.display.update()
for event in pygame.event.get(): # if we hit "x" to close out the game, close out the game.
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.MOUSEBUTTONDOWN:
main()
pygame.quit()
main_menu(width, height)```
The bottleneck in your game is the function draw_grid, which draws far too many lines out of the window.
def draw_grid(window, height, width, color):
x = 0
y = 0
grid_blocks = 20
for i in range(height):
x += 20
pygame.draw.line(window, color, (x, 0), (x, height), 1)
for j in range(width):
y += 20
pygame.draw.line(window, color, (0, y), (height, y), 1)
If you draw a line outside the window, the statement does not draw anything, nevertheless the nested for loops still run.
Furthermore, you don't need a nested loops. You don't want to draw 19 horizontal lines for each vertical line. You want to draw 19 vertical and 19 horizontal lines. Hence 1 for-loop is enough.
Use the step argument of range to define the list of positions for the vertical and horizontal lines
def draw_grid(window, height, width, color):
tile_size = 20
for p in range(tile_size, height, tile_size):
pygame.draw.line(window, color, (p, 0), (p, height), 1)
pygame.draw.line(window, color, (0, p), (height, p), 1)
if the size is 400 * 400 pixels, the total pixels 160000 so moving at a 20 pixel rate, because there are less pixels the an 800 * 800 board (320000 pixels) it looks like you are going faster because there are less pixels. Find an equation to calculate the correct speed on differently sized boards.
Sincerely,
Zac

Pygame sprite sheet hitbox broken

I made a simple game, where you have to jump over the stones moving towards you. The problem is that one of those sprites hitboxes is broken, even if the sprite mask is used. The player sprite is generated just dividing the player sprite sheet into 4 columns. Maybe there the problem is hiding?
Pictures for the program:
https://drive.google.com/drive/folders/1We6RDMs3Cwwprf1OY0ow9WCTHF9MHzne?usp=sharing
import pygame, os, random
pygame.init()
W, H = 800,600
HW, HH = W/2,H/2
AREA = W * H
WHITE = (255,255,255)
GREEN = (69,139,0)
FPS = 60
bg = pygame.image.load(os.path.join('Pildid', 'Taust3.png'))
bg = pygame.transform.scale(bg, (2000, 600))
DS = pygame.display.set_mode((W,H))
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, py, player, veerg, rida):
super(Player,self).__init__()
'''Mangija huppamine'''
clock.tick(5)
self.x = x
self.y = y
self.jumping = False
self.platform_y = py
self.velocity_index = 0
'''Sprite sheet'''
self.player = pygame.image.load(os.path.join('Pildid', 'karakter.png')).convert_alpha()#pildi uleslaadimine
#self.player = pygame.transform.scale(self.player,(200,100))
self.rect = self.player.get_rect()
'''Sprite sheeti piltide jaotamine pikslite jargi'''
self.veerg = veerg
self.rida = rida
self.kokku = veerg * rida
self.rect = self.player.get_rect()
L = self.veergL = self.rect.width/veerg
K = self.weegK = self.rect.height/rida
PL,PK = self.veergKeskel = (L/2,K/2)
self.veerg = list([(index % veerg * L, int(index/veerg) * K,L,K )for index in range(self.kokku)])
self.handle = list([ #pildi paigutamise voimalikud positsioonid
(0, 0), (-PL, 0), (-L, 0),
(0, -PK), (-PL, -PK), (-L, -PK),
(0, -L), (-PL, -K), (-L, -K),])
self.mask = pygame.mask.from_surface(self.player)
def do_jumpt(self):
'''Huppamine: kiirus, korgus, platvorm'''
global velocity
if self.jumping:
self.y += velocity[self.velocity_index]
self.velocity_index += 1
if self.velocity_index >= len(velocity) - 1:
self.velocity_index = len(velocity) - 1
if self.y > self.platform_y:
self.y = self.platform_y
self.jumping = False
self.velocity_index = 0
def draw(self, DS,veergindex,x,y,handle=0):
DS.blit(self.player,(self.x+self.handle[handle][0], self.y + self.handle[handle][1]),self.veerg[veergindex])
def do(self):
'''Funktsioonide kokkupanek'''
self.do_jumpt()
p.draw(DS,index%p.kokku,190, 359,4)
def update(self):
self.rect.center = self.x, self.y
def keys(player):
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE] or keys[pygame.K_UP] and player.jumping == False:
player.jumping = True
class Obsticles(pygame.sprite.Sprite):
'''Game obsticles: **'''
#img = pygame.image.load(os.path.join('images', 'box.png'))
def __init__(self, x, y):
super(Obsticles,self).__init__()
self.img = pygame.image.load(os.path.join('Pildid', 'kivi.png')).convert_alpha()
self.img = pygame.transform.scale(self.img, (90,90))
self.rect = self.img.get_rect(center=(x, y))
self.x = x
self.y = y
self.mask = pygame.mask.from_surface(self.img)
def draw(self, DS):
'''Obsticle img blitting and hitbox'''
DS.blit(self.img, (self.x, self.y))
def update(self):
if self.x < -64: # Delete the obstacle.
# `kill()` removes this obstacle sprite from all sprite groups.
self.kill()
self.x += speed # Move the obstacle.
# Update the rect because it's used to blit the
# sprite and for the collision detection.
self.rect.center = self.x, self.y
def redrawWindow():
'''Obsticle loop'''
for i in ob:
i.draw(DS)
def text_objects(text, font):
textSurface = font.render(text, True, WHITE)
return textSurface, textSurface.get_rect()
def message_display(text):
largeText = pygame.font.Font('freesansbold.ttf',60)
TextSurf, TextRect = text_objects(text, largeText)
TextRect.center = ((W/2),(H/4))
DS.blit(TextSurf, TextRect)
pygame.display.update()
pygame.time.wait(3000)
def crash():
message_display('Failed')
pygame.time.set_timer(pygame.USEREVENT+2, random.choice([2500, 3000, 1500]))
velocity = list([(i/ 1)-20 for i in range (0,60)]) #Huppe ulatus
index = 3
obsticles = Obsticles(832, 363)
p = Player(190, 359, 359, 'karakter.png', 4, 1)
all_sprites = pygame.sprite.Group(p, obsticles)
ob = pygame.sprite.Group(obsticles)
x = 0
x -= 1
speed = -5
running = True
while running:
# ---Handle the events.---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.USEREVENT+2:
r = random.randrange(0, 2)
if r == 0:
obsticles = Obsticles(832, 363)
# Add the obstacle to both groups.
ob.add(obsticles)
all_sprites.add(obsticles)
speed += -0.008
# ---Game logic.---
all_sprites.update()
collided = pygame.sprite.spritecollide(p, ob, True,
pygame.sprite.collide_mask)
if collided:
crash()
index += 1
# Background movement.
back_x = x % bg.get_rect().width
x -= 2
# ---Draw everything.---
DS.blit(bg, (back_x - bg.get_rect().width, 0))
if back_x < W:
DS.blit(bg, (back_x, 0))
keys(p)
p.do()
redrawWindow()
pygame.display.update()
clock.tick(60)
pygame.quit()
quit()
The problem is that you use the original image to create the Player.mask. The resulting mask covers all four poses of the character, so even if only one of the sprite sheet frames is visible, the complete original image is used for the collision detection. Your mask looks like the green area in this picture:
You should also use the rect (i.e. the rect.topleft coords) as the blit position, because it is used for the collision detection as well (to find the location of the mask).
I've got a fixed, shortened version of your program in which I've changed a few things to show you how I would solve the problem. First of all, I load the sprite sheet and cut it into several subsurfaces, then I assign the first surface/image to the self.image attribute of the Player. This image can be used to create the rect and the mask.
In the update method I increment the frame_index and assign the current image to self.image in order to animate the sprite. BTW, it would be a good idea to create separate masks for every image and swap them as well during the animation. (I'm just using the first mask here, since the animation frames are so similar.)
Note that if you give your sprites an image and a rect attribute, you can just call all_sprites.update(DS) to blit all sprite images at their corresponding rects.
import random
import pygame
pygame.init()
FPS = 60
DS = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
PLAYER_SHEET = pygame.image.load('karakter.png').convert_alpha()
# Cut the sprite sheet and append the subsurfaces to this list.
PLAYER_IMAGES = []
width = PLAYER_SHEET.get_width() / 4
height = PLAYER_SHEET.get_height()
for x in range(4):
PLAYER_IMAGES.append(PLAYER_SHEET.subsurface(x*width, 0, width, height))
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, py):
super(Player,self).__init__()
self.x = x
self.y = y
self.jumping = False
self.platform_y = py
self.velocity_index = 0
self.velocity = list([(i/ 1)-20 for i in range (0,60)])
self.frame_index = 0
self.image = PLAYER_IMAGES[self.frame_index]
self.rect = self.image.get_rect()
self.mask = pygame.mask.from_surface(self.image)
def do_jump(self):
if self.jumping:
self.y += self.velocity[self.velocity_index]
self.velocity_index += 1
if self.velocity_index >= len(self.velocity) - 1:
self.velocity_index = len(self.velocity) - 1
if self.y > self.platform_y:
self.y = self.platform_y
self.jumping = False
self.velocity_index = 0
def update(self):
self.rect.center = self.x, self.y
self.do_jump()
# Update the animation frame.
self.frame_index += 1
self.frame_index %= len(PLAYER_IMAGES) * 7 # * 7 to slow it down.
# Swap the image.
self.image = PLAYER_IMAGES[self.frame_index//7] # // 7 to slow it down.
def keys(player):
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE] or keys[pygame.K_UP] and player.jumping == False:
player.jumping = True
class Obstacle(pygame.sprite.Sprite):
"""Game Obstacle."""
def __init__(self, x, y):
super(Obstacle,self).__init__()
self.image = pygame.Surface((90, 90), pygame.SRCALPHA)
self.image.fill((100, 150, 0))
self.image = pygame.transform.scale(self.image, (90,90))
self.rect = self.image.get_rect(center=(x, y))
self.x = x
self.y = y
self.mask = pygame.mask.from_surface(self.image)
def update(self):
if self.x < -64:
self.kill()
self.x += speed
self.rect.center = self.x, self.y
pygame.time.set_timer(pygame.USEREVENT+2, random.choice([2500, 3000, 1500]))
index = 3
obstacle = Obstacle(832, 363)
player = Player(190, 359, 359)
all_sprites = pygame.sprite.Group(player, obstacle)
obstacles = pygame.sprite.Group(obstacle)
speed = -5
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.USEREVENT+2:
r = random.randrange(0, 2)
if r == 0:
obstacle = Obstacle(832, 363)
# Add the obstacle to both groups.
obstacles.add(obstacle)
all_sprites.add(obstacle)
keys(player)
# ---Game logic.---
speed += -0.008
all_sprites.update()
collided = pygame.sprite.spritecollide(player, obstacles, True,
pygame.sprite.collide_mask)
if collided:
print('crash')
index += 1
# ---Draw everything.---
DS.fill((30, 30, 30))
all_sprites.draw(DS)
# Here I draw the outline (points) of the player's mask.
px, py = player.rect.topleft
for point in player.mask.outline(8):
x, y = point[0] + px, point[1] + py
pygame.draw.circle(DS, (250, 250, 0), (x, y), 2)
pygame.draw.rect(DS, (0, 0, 250), player.rect, 1)
# Draw the outlines of the obstacles.
for obstac in obstacles:
pygame.draw.rect(DS, (150, 250, 0), obstac.rect, 1)
ox, oy = obstac.rect.topleft
for point in obstac.mask.outline(6):
pygame.draw.circle(DS, (250, 0, 0), (point[0]+ox, point[1]+oy), 2)
pygame.display.update()
clock.tick(60)
pygame.quit()

Categories

Resources