text box missing keystrokes [duplicate] - python-3.x

I want to get some text input from the user in Python and display what they are typing in a text box, and when they press enter, it gets stored in a string.
I've looked everywhere, but I just can't find anything. I'm using Pygame.

You can define a rect as the area of the input box. If a pygame.MOUSEBUTTONDOWN event occurs, use the colliderect method of the input_box rect to check if it collides with the event.pos and then activate it by setting a active variable to True.
If the box is active you can type something and Pygame will generate pygame.KEYDOWN events which have a unicode attribute that you can simply add to a string, e.g. text += event.unicode. If the user presses enter, you can do something with the text string (in the example I just print it) and reset it to ''.
import pygame as pg
def main():
screen = pg.display.set_mode((640, 480))
font = pg.font.Font(None, 32)
clock = pg.time.Clock()
input_box = pg.Rect(100, 100, 140, 32)
color_inactive = pg.Color('lightskyblue3')
color_active = pg.Color('dodgerblue2')
color = color_inactive
active = False
text = ''
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if event.type == pg.MOUSEBUTTONDOWN:
# If the user clicked on the input_box rect.
if input_box.collidepoint(event.pos):
# Toggle the active variable.
active = not active
else:
active = False
# Change the current color of the input box.
color = color_active if active else color_inactive
if event.type == pg.KEYDOWN:
if active:
if event.key == pg.K_RETURN:
print(text)
text = ''
elif event.key == pg.K_BACKSPACE:
text = text[:-1]
else:
text += event.unicode
screen.fill((30, 30, 30))
# Render the current text.
txt_surface = font.render(text, True, color)
# Resize the box if the text is too long.
width = max(200, txt_surface.get_width()+10)
input_box.w = width
# Blit the text.
screen.blit(txt_surface, (input_box.x+5, input_box.y+5))
# Blit the input_box rect.
pg.draw.rect(screen, color, input_box, 2)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Here's an object-oriented variant that allows you to easily create multiple input boxes:
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
COLOR_INACTIVE = pg.Color('lightskyblue3')
COLOR_ACTIVE = pg.Color('dodgerblue2')
FONT = pg.font.Font(None, 32)
class InputBox:
def __init__(self, x, y, w, h, text=''):
self.rect = pg.Rect(x, y, w, h)
self.color = COLOR_INACTIVE
self.text = text
self.txt_surface = FONT.render(text, True, self.color)
self.active = False
def handle_event(self, event):
if event.type == pg.MOUSEBUTTONDOWN:
# If the user clicked on the input_box rect.
if self.rect.collidepoint(event.pos):
# Toggle the active variable.
self.active = not self.active
else:
self.active = False
# Change the current color of the input box.
self.color = COLOR_ACTIVE if self.active else COLOR_INACTIVE
if event.type == pg.KEYDOWN:
if self.active:
if event.key == pg.K_RETURN:
print(self.text)
self.text = ''
elif event.key == pg.K_BACKSPACE:
self.text = self.text[:-1]
else:
self.text += event.unicode
# Re-render the text.
self.txt_surface = FONT.render(self.text, True, self.color)
def update(self):
# Resize the box if the text is too long.
width = max(200, self.txt_surface.get_width()+10)
self.rect.w = width
def draw(self, screen):
# Blit the text.
screen.blit(self.txt_surface, (self.rect.x+5, self.rect.y+5))
# Blit the rect.
pg.draw.rect(screen, self.color, self.rect, 2)
def main():
clock = pg.time.Clock()
input_box1 = InputBox(100, 100, 140, 32)
input_box2 = InputBox(100, 300, 140, 32)
input_boxes = [input_box1, input_box2]
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
for box in input_boxes:
box.handle_event(event)
for box in input_boxes:
box.update()
screen.fill((30, 30, 30))
for box in input_boxes:
box.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
There are also third party modules available like pygame_textinput.

Use the KEYDOWN event to get the input from the keyboard (see pygame.event). The key that was pressed can be obtained from the key attribute of the pygame.event.Event object. unicode contains a single character string that is the fully translated character. Add the character to the text when a key is pressed.
Two special keys need to be dealt with. If RETURN is pressed, the input is finished. If BACKSPACE is pressed, the last character of the input text must be removed:
repl.it/#Rabbid76/PyGame-TextInput
import pygame
pygame.init()
window = pygame.display.set_mode((500, 200))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
text = ""
input_active = True
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEBUTTONDOWN:
input_active = True
text = ""
elif event.type == pygame.KEYDOWN and input_active:
if event.key == pygame.K_RETURN:
input_active = False
elif event.key == pygame.K_BACKSPACE:
text = text[:-1]
else:
text += event.unicode
window.fill(0)
text_surf = font.render(text, True, (255, 0, 0))
window.blit(text_surf, text_surf.get_rect(center = window.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
Use the algorithm in a pygame.sprite.Sprite class. Handle the event in the update method.Determine whether the mouse clicks in the text entry field with collidepoint (see How to detect when a rectangular object, image or sprite is clicked) and activate the text input box:
class TextInputBox(pygame.sprite.Sprite):
# [...]
def update(self, event_list):
for event in event_list:
if event.type == pygame.MOUSEBUTTONDOWN and not self.active:
self.active = self.rect.collidepoint(event.pos)
if event.type == pygame.KEYDOWN and self.active:
if event.key == pygame.K_RETURN:
self.active = False
elif event.key == pygame.K_BACKSPACE:
self.text = self.text[:-1]
else:
self.text += event.unicode
self.render_text()
Pass the list of events to the update method of the Group that contains the Sprite:
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
run = False
group.update(event_list)
Minimal example: repl.it/#Rabbid76/PyGame-SpriteTextInput
import pygame
class TextInputBox(pygame.sprite.Sprite):
def __init__(self, x, y, w, font):
super().__init__()
self.color = (255, 255, 255)
self.backcolor = None
self.pos = (x, y)
self.width = w
self.font = font
self.active = False
self.text = ""
self.render_text()
def render_text(self):
t_surf = self.font.render(self.text, True, self.color, self.backcolor)
self.image = pygame.Surface((max(self.width, t_surf.get_width()+10), t_surf.get_height()+10), pygame.SRCALPHA)
if self.backcolor:
self.image.fill(self.backcolor)
self.image.blit(t_surf, (5, 5))
pygame.draw.rect(self.image, self.color, self.image.get_rect().inflate(-2, -2), 2)
self.rect = self.image.get_rect(topleft = self.pos)
def update(self, event_list):
for event in event_list:
if event.type == pygame.MOUSEBUTTONDOWN and not self.active:
self.active = self.rect.collidepoint(event.pos)
if event.type == pygame.KEYDOWN and self.active:
if event.key == pygame.K_RETURN:
self.active = False
elif event.key == pygame.K_BACKSPACE:
self.text = self.text[:-1]
else:
self.text += event.unicode
self.render_text()
pygame.init()
window = pygame.display.set_mode((500, 200))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
text_input_box = TextInputBox(50, 50, 400, font)
group = pygame.sprite.Group(text_input_box)
run = True
while run:
clock.tick(60)
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
run = False
group.update(event_list)
window.fill(0)
group.draw(window)
pygame.display.flip()
pygame.quit()
exit()

You can find a great module for Pygame text input here.
I have been using it for a while and I really like it. A tutorial how to use it is included in the description.
However, I have added the possibility to draw a (coloured) rectangle around the text, by adding a rect and a rect_color parameter to the *_init_() function and adding
if self.rect != None:
pygame.draw.rect(screen, self.rect_color, self.rect) #screen is my pygame display surface
to the update(self, events) function.

The pygame_gui module allows you to create a text_input box from the user by creating a UITextEntryLine instance. You'll need to set up an instance as in the quick start guide.
Create the text_input:
from pygame.rect import Rect
from pygame_gui.elements.ui_text_entry_line import UITextEntryLine
text_input = UITextEntryLine(relative_rect=Rect(0, 0, 100, 100), manager=manager)
Get the text if enter is clicked:
for event in pygame.event.get():
if event.type == pygame.USEREVENT:
if event.user_type == pygame_gui.UI_TEXT_ENTRY_FINISHED:
if event.ui_element == text_input:
entered_text = event.text

I have written a class that can handle text input
minimal example:
import pygame as pg
from pgtextbox import pgtextbox
pg.init()
screen=pg.display.set_mode((1000,500))
textbox=pgtextbox(200,20)
textbox.insertAtCurser('Hallo')
while True:
e = pg.event.wait(30000)
if e.type == pg.QUIT:
raise StopIteration
textbox.addPgEvent(e)#uses keydown events
print(textbox.text)
screen.fill((0,0,0))
screen.blit(textbox.render(),(10,0))
pg.display.flip()
pg.display.quit()
pgtextbox class:
import pygame as pg
class pgtextbox:#By K1521
def __init__(self,width=100,height=10,fontname=None):
self.surface=pg.Surface((width,height))
self.text=""
self.width=width
self.height=height
self.font=pg.font.Font(fontname,pgtextbox.getMaxFontSize(fontname,lineheight=height))
self.curserindex=0
self.cursersurface=pg.Surface((self.font.size("|")[0]//2,self.font.size("|")[1]))
self.cursersurface.fill((255,255,255))
#self.cursersurface=self.font.render("|",False,(255,255,255),(0,0,0))
self.offsety=int((height-self.font.get_linesize())/2)
self.offsetx=0
def curserpos(self):
return self.font.size(self.text[:self.curserindex])[0]
def addPgEvent(self,event):
if event.type==pg.KEYDOWN:
if event.key==pg.K_BACKSPACE:
self.deleteAtCurser()
elif event.key==pg.K_RIGHT:
self.offsetCurser(1)
elif event.key==pg.K_LEFT:
self.offsetCurser(-1)
else:
self.insertAtCurser(event.unicode)
def render(self):
self.surface.fill((0,0,0))
width=self.width-self.cursersurface.get_width()
text=self.font.render(self.text,False,(255,255,255),(0,0,0))
if self.curserindex>=0:
curserpos=self.curserpos()+self.offsetx
curserposnew=max(0,min(curserpos,width))
self.offsetx+=curserposnew-curserpos
curserpos=curserposnew
#if curserpos<0:
#self.offsetx-=curserpos
#curserpos=0
#if curserpos>width:
#curserpos=curserpos-width
#self.offsetx-=curserpos
else:
#self.offsetx=min(width-text.get_width(),0)
self.offsetx=0
self.surface.blit(text,(self.offsetx,self.offsety))
if self.curserindex>=0:
self.surface.blit(self.cursersurface,(curserpos,self.offsety))
#print((curserpos,self.offsety))
return self.surface
def insertAtCurser(self,t):
if self.curserindex<0:
self.curserindex=len(self.text)
self.text=self.text[:self.curserindex]+t+self.text[self.curserindex:]
self.curserindex+=len(t)
def deleteAtCurser(self,length=1):
if self.curserindex<0:
self.curserindex=len(self.text)
newcurserindex=max(0,self.curserindex-length)
self.text=self.text[:newcurserindex]+self.text[self.curserindex:]
self.curserindex=newcurserindex
def offsetCurser(self,i):
self.curserindex=max(min(self.curserindex+i,len(self.text)),0)
#staticmethod
def longestline(self,fontname,lines):
size=pg.font.Font(fontname,1000)
return max(lines,key=lambda t:size(t)[0])
#staticmethod
def getMaxFontSize(fontname,width=None,lineheight=None,line=None):
def font(size):
return pg.font.Font(fontname,size)
fontsize=float("inf")# inf
if width:
aproxsize=width*1000//font(1000).size(line)[0]
while font(aproxsize).size(line)[0]<width:
aproxsize+=1
while font(aproxsize).size(line)[0]>width:
aproxsize-=1
fontsize=min(aproxsize,fontsize)
if lineheight:
aproxsize=lineheight*4//3
while font(aproxsize).get_linesize()<lineheight:
aproxsize+=1
while font(aproxsize).get_linesize()>lineheight:
aproxsize-=1
fontsize=min(aproxsize,fontsize)
return fontsize
#staticmethod
def rendermultilinetext(text,width=None,height=10,fontname=None,antialias=False,color=(255,255,255),background=None):
if(len(text)-text.count("\n")==0):
return pg.Surface((0,0))
def font(size):
return pg.font.Font(fontname,size)
text=text.split("\n")
fontsize=1000000000# inf
longestline=None
if height:
longestline=pgtextbox.longestline(fontname,lines)
fontsize=pgtextbox.getMaxFontSize(fontname,width,lineheight,longestline)
font=font(fontsize)
width=font.size(longestline)[0]
lineheight=font.get_linesize()
heigth=len(text)*lineheight
textsurface=pg.Surface((width,heigth))
if background:
textsurface.fill(background)
for i,line in enumerate(text):
textsurface.blit(font.render(line,antialias,color,background),(0,i*lineheight))
return textsurface

Related

My Text Input Fields class isn't working and I don't know what I am missing [duplicate]

I want to get some text input from the user in Python and display what they are typing in a text box, and when they press enter, it gets stored in a string.
I've looked everywhere, but I just can't find anything. I'm using Pygame.
You can define a rect as the area of the input box. If a pygame.MOUSEBUTTONDOWN event occurs, use the colliderect method of the input_box rect to check if it collides with the event.pos and then activate it by setting a active variable to True.
If the box is active you can type something and Pygame will generate pygame.KEYDOWN events which have a unicode attribute that you can simply add to a string, e.g. text += event.unicode. If the user presses enter, you can do something with the text string (in the example I just print it) and reset it to ''.
import pygame as pg
def main():
screen = pg.display.set_mode((640, 480))
font = pg.font.Font(None, 32)
clock = pg.time.Clock()
input_box = pg.Rect(100, 100, 140, 32)
color_inactive = pg.Color('lightskyblue3')
color_active = pg.Color('dodgerblue2')
color = color_inactive
active = False
text = ''
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if event.type == pg.MOUSEBUTTONDOWN:
# If the user clicked on the input_box rect.
if input_box.collidepoint(event.pos):
# Toggle the active variable.
active = not active
else:
active = False
# Change the current color of the input box.
color = color_active if active else color_inactive
if event.type == pg.KEYDOWN:
if active:
if event.key == pg.K_RETURN:
print(text)
text = ''
elif event.key == pg.K_BACKSPACE:
text = text[:-1]
else:
text += event.unicode
screen.fill((30, 30, 30))
# Render the current text.
txt_surface = font.render(text, True, color)
# Resize the box if the text is too long.
width = max(200, txt_surface.get_width()+10)
input_box.w = width
# Blit the text.
screen.blit(txt_surface, (input_box.x+5, input_box.y+5))
# Blit the input_box rect.
pg.draw.rect(screen, color, input_box, 2)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Here's an object-oriented variant that allows you to easily create multiple input boxes:
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
COLOR_INACTIVE = pg.Color('lightskyblue3')
COLOR_ACTIVE = pg.Color('dodgerblue2')
FONT = pg.font.Font(None, 32)
class InputBox:
def __init__(self, x, y, w, h, text=''):
self.rect = pg.Rect(x, y, w, h)
self.color = COLOR_INACTIVE
self.text = text
self.txt_surface = FONT.render(text, True, self.color)
self.active = False
def handle_event(self, event):
if event.type == pg.MOUSEBUTTONDOWN:
# If the user clicked on the input_box rect.
if self.rect.collidepoint(event.pos):
# Toggle the active variable.
self.active = not self.active
else:
self.active = False
# Change the current color of the input box.
self.color = COLOR_ACTIVE if self.active else COLOR_INACTIVE
if event.type == pg.KEYDOWN:
if self.active:
if event.key == pg.K_RETURN:
print(self.text)
self.text = ''
elif event.key == pg.K_BACKSPACE:
self.text = self.text[:-1]
else:
self.text += event.unicode
# Re-render the text.
self.txt_surface = FONT.render(self.text, True, self.color)
def update(self):
# Resize the box if the text is too long.
width = max(200, self.txt_surface.get_width()+10)
self.rect.w = width
def draw(self, screen):
# Blit the text.
screen.blit(self.txt_surface, (self.rect.x+5, self.rect.y+5))
# Blit the rect.
pg.draw.rect(screen, self.color, self.rect, 2)
def main():
clock = pg.time.Clock()
input_box1 = InputBox(100, 100, 140, 32)
input_box2 = InputBox(100, 300, 140, 32)
input_boxes = [input_box1, input_box2]
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
for box in input_boxes:
box.handle_event(event)
for box in input_boxes:
box.update()
screen.fill((30, 30, 30))
for box in input_boxes:
box.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
There are also third party modules available like pygame_textinput.
Use the KEYDOWN event to get the input from the keyboard (see pygame.event). The key that was pressed can be obtained from the key attribute of the pygame.event.Event object. unicode contains a single character string that is the fully translated character. Add the character to the text when a key is pressed.
Two special keys need to be dealt with. If RETURN is pressed, the input is finished. If BACKSPACE is pressed, the last character of the input text must be removed:
repl.it/#Rabbid76/PyGame-TextInput
import pygame
pygame.init()
window = pygame.display.set_mode((500, 200))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
text = ""
input_active = True
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEBUTTONDOWN:
input_active = True
text = ""
elif event.type == pygame.KEYDOWN and input_active:
if event.key == pygame.K_RETURN:
input_active = False
elif event.key == pygame.K_BACKSPACE:
text = text[:-1]
else:
text += event.unicode
window.fill(0)
text_surf = font.render(text, True, (255, 0, 0))
window.blit(text_surf, text_surf.get_rect(center = window.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
Use the algorithm in a pygame.sprite.Sprite class. Handle the event in the update method.Determine whether the mouse clicks in the text entry field with collidepoint (see How to detect when a rectangular object, image or sprite is clicked) and activate the text input box:
class TextInputBox(pygame.sprite.Sprite):
# [...]
def update(self, event_list):
for event in event_list:
if event.type == pygame.MOUSEBUTTONDOWN and not self.active:
self.active = self.rect.collidepoint(event.pos)
if event.type == pygame.KEYDOWN and self.active:
if event.key == pygame.K_RETURN:
self.active = False
elif event.key == pygame.K_BACKSPACE:
self.text = self.text[:-1]
else:
self.text += event.unicode
self.render_text()
Pass the list of events to the update method of the Group that contains the Sprite:
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
run = False
group.update(event_list)
Minimal example: repl.it/#Rabbid76/PyGame-SpriteTextInput
import pygame
class TextInputBox(pygame.sprite.Sprite):
def __init__(self, x, y, w, font):
super().__init__()
self.color = (255, 255, 255)
self.backcolor = None
self.pos = (x, y)
self.width = w
self.font = font
self.active = False
self.text = ""
self.render_text()
def render_text(self):
t_surf = self.font.render(self.text, True, self.color, self.backcolor)
self.image = pygame.Surface((max(self.width, t_surf.get_width()+10), t_surf.get_height()+10), pygame.SRCALPHA)
if self.backcolor:
self.image.fill(self.backcolor)
self.image.blit(t_surf, (5, 5))
pygame.draw.rect(self.image, self.color, self.image.get_rect().inflate(-2, -2), 2)
self.rect = self.image.get_rect(topleft = self.pos)
def update(self, event_list):
for event in event_list:
if event.type == pygame.MOUSEBUTTONDOWN and not self.active:
self.active = self.rect.collidepoint(event.pos)
if event.type == pygame.KEYDOWN and self.active:
if event.key == pygame.K_RETURN:
self.active = False
elif event.key == pygame.K_BACKSPACE:
self.text = self.text[:-1]
else:
self.text += event.unicode
self.render_text()
pygame.init()
window = pygame.display.set_mode((500, 200))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
text_input_box = TextInputBox(50, 50, 400, font)
group = pygame.sprite.Group(text_input_box)
run = True
while run:
clock.tick(60)
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
run = False
group.update(event_list)
window.fill(0)
group.draw(window)
pygame.display.flip()
pygame.quit()
exit()
You can find a great module for Pygame text input here.
I have been using it for a while and I really like it. A tutorial how to use it is included in the description.
However, I have added the possibility to draw a (coloured) rectangle around the text, by adding a rect and a rect_color parameter to the *_init_() function and adding
if self.rect != None:
pygame.draw.rect(screen, self.rect_color, self.rect) #screen is my pygame display surface
to the update(self, events) function.
The pygame_gui module allows you to create a text_input box from the user by creating a UITextEntryLine instance. You'll need to set up an instance as in the quick start guide.
Create the text_input:
from pygame.rect import Rect
from pygame_gui.elements.ui_text_entry_line import UITextEntryLine
text_input = UITextEntryLine(relative_rect=Rect(0, 0, 100, 100), manager=manager)
Get the text if enter is clicked:
for event in pygame.event.get():
if event.type == pygame.USEREVENT:
if event.user_type == pygame_gui.UI_TEXT_ENTRY_FINISHED:
if event.ui_element == text_input:
entered_text = event.text
I have written a class that can handle text input
minimal example:
import pygame as pg
from pgtextbox import pgtextbox
pg.init()
screen=pg.display.set_mode((1000,500))
textbox=pgtextbox(200,20)
textbox.insertAtCurser('Hallo')
while True:
e = pg.event.wait(30000)
if e.type == pg.QUIT:
raise StopIteration
textbox.addPgEvent(e)#uses keydown events
print(textbox.text)
screen.fill((0,0,0))
screen.blit(textbox.render(),(10,0))
pg.display.flip()
pg.display.quit()
pgtextbox class:
import pygame as pg
class pgtextbox:#By K1521
def __init__(self,width=100,height=10,fontname=None):
self.surface=pg.Surface((width,height))
self.text=""
self.width=width
self.height=height
self.font=pg.font.Font(fontname,pgtextbox.getMaxFontSize(fontname,lineheight=height))
self.curserindex=0
self.cursersurface=pg.Surface((self.font.size("|")[0]//2,self.font.size("|")[1]))
self.cursersurface.fill((255,255,255))
#self.cursersurface=self.font.render("|",False,(255,255,255),(0,0,0))
self.offsety=int((height-self.font.get_linesize())/2)
self.offsetx=0
def curserpos(self):
return self.font.size(self.text[:self.curserindex])[0]
def addPgEvent(self,event):
if event.type==pg.KEYDOWN:
if event.key==pg.K_BACKSPACE:
self.deleteAtCurser()
elif event.key==pg.K_RIGHT:
self.offsetCurser(1)
elif event.key==pg.K_LEFT:
self.offsetCurser(-1)
else:
self.insertAtCurser(event.unicode)
def render(self):
self.surface.fill((0,0,0))
width=self.width-self.cursersurface.get_width()
text=self.font.render(self.text,False,(255,255,255),(0,0,0))
if self.curserindex>=0:
curserpos=self.curserpos()+self.offsetx
curserposnew=max(0,min(curserpos,width))
self.offsetx+=curserposnew-curserpos
curserpos=curserposnew
#if curserpos<0:
#self.offsetx-=curserpos
#curserpos=0
#if curserpos>width:
#curserpos=curserpos-width
#self.offsetx-=curserpos
else:
#self.offsetx=min(width-text.get_width(),0)
self.offsetx=0
self.surface.blit(text,(self.offsetx,self.offsety))
if self.curserindex>=0:
self.surface.blit(self.cursersurface,(curserpos,self.offsety))
#print((curserpos,self.offsety))
return self.surface
def insertAtCurser(self,t):
if self.curserindex<0:
self.curserindex=len(self.text)
self.text=self.text[:self.curserindex]+t+self.text[self.curserindex:]
self.curserindex+=len(t)
def deleteAtCurser(self,length=1):
if self.curserindex<0:
self.curserindex=len(self.text)
newcurserindex=max(0,self.curserindex-length)
self.text=self.text[:newcurserindex]+self.text[self.curserindex:]
self.curserindex=newcurserindex
def offsetCurser(self,i):
self.curserindex=max(min(self.curserindex+i,len(self.text)),0)
#staticmethod
def longestline(self,fontname,lines):
size=pg.font.Font(fontname,1000)
return max(lines,key=lambda t:size(t)[0])
#staticmethod
def getMaxFontSize(fontname,width=None,lineheight=None,line=None):
def font(size):
return pg.font.Font(fontname,size)
fontsize=float("inf")# inf
if width:
aproxsize=width*1000//font(1000).size(line)[0]
while font(aproxsize).size(line)[0]<width:
aproxsize+=1
while font(aproxsize).size(line)[0]>width:
aproxsize-=1
fontsize=min(aproxsize,fontsize)
if lineheight:
aproxsize=lineheight*4//3
while font(aproxsize).get_linesize()<lineheight:
aproxsize+=1
while font(aproxsize).get_linesize()>lineheight:
aproxsize-=1
fontsize=min(aproxsize,fontsize)
return fontsize
#staticmethod
def rendermultilinetext(text,width=None,height=10,fontname=None,antialias=False,color=(255,255,255),background=None):
if(len(text)-text.count("\n")==0):
return pg.Surface((0,0))
def font(size):
return pg.font.Font(fontname,size)
text=text.split("\n")
fontsize=1000000000# inf
longestline=None
if height:
longestline=pgtextbox.longestline(fontname,lines)
fontsize=pgtextbox.getMaxFontSize(fontname,width,lineheight,longestline)
font=font(fontsize)
width=font.size(longestline)[0]
lineheight=font.get_linesize()
heigth=len(text)*lineheight
textsurface=pg.Surface((width,heigth))
if background:
textsurface.fill(background)
for i,line in enumerate(text):
textsurface.blit(font.render(line,antialias,color,background),(0,i*lineheight))
return textsurface

PyGame Screen Returning Black when run

While trying to run a game in which is being developed for a project, the code would not work and causes the screen to be black. The Mycharacter is a character, enemy is for a enemy character, obstacle is for obstacles within the game. The design is supposed to be a character chasing the enemy while the enemy is chasing the character to gain points with obstructions in the way.
We are importing other classes into this function and main class.
All help will be appricated.
Heres how the code looks:
import pygame
import time
import random
import Mycharacter
import enemy
import obstacle
import json
GREEN = (0, 255, 0)
class Controller:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((800, 800))
self.background = pygame.Surface(self.screen.get_size()).convert()
#self.button_image = pygame.image.load("start_button.png").convert
self.button = pygame.Rect(350, 50, 100, 100)
#self.button_image.get_rect()
self.font = pygame.font.Font("Shark_Soft_Bites.TTF", 60)
self.text = pygame.font.Font.render(self.font,"Start", True, (255, 0, 0))
#self.score_text = pygame.font.Font.render("Highscore: "+ str())
self.file = open("highscores.json", "r")
self.currentState = "Start"
self.obstacles = []
for i in range(5):
x = random.randrange(50, 600)
y = random.randrange(60, 700)
self.obstacles.append(obstacle.Obstacle((x, y), 'rock.png' ))
self.enemies = []
for i in range(3) :
if i == 0:
x = 725
y = 100
elif i == 1:
x = 75
y = 700
elif i == 2:
x = 725
y = 700
self.enemies.append(enemy.Enemy((x, y), "enemy.png"))
self.Mycharacter = Mycharacter.Mycharacter((75, 100), "head6.png")
self.mysprites = pygame.sprite.Group((self.Mycharacter,) + tuple(self.enemies) + tuple(self.obstacles))
self.mysprites2 = pygame.sprite.Group(tuple(self.enemies) + tuple(self.obstacles))
self.score = 0
self.end_time = 0
self.start_time = 0
self.time = (self.end_time-self.start_time) * 1000
def mainLoop(self):
"""
This is the main loop for the game that calls the other functions in this class
in order to create a start screen, run the game, and present the high score and player
score at the end of the game.
Param list: None
Return list: None
"""
self.done = False
while not self.done:
if self.currentState == "start":
self.startGame(self)
elif self.currentState == "running":
self.start_time = pygame.time.get_ticks()
self.runGame(self)
elif self.currentState == "end":
self.endGame(self)
pygame.quit
def startGame(self):
"""
This is the function for the start of the game. It fills the screen
with a different color background and creates the start button that the user
can click on to begin running the game.
Param list: None
Returns: None
"""
start = True
while start:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
start = False
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
if self.button.collidepoint(mouse_pos):
self.currentState = "running"
start = False
self.screen.fill((255, 0, 255))
pygame.draw.rect(self.screen, (0, 0, 0), self.button)
self.screen.blit(self.text, (350,50))
pygame.display.flip()
pygame.quit
def runGame(self):
run = True
clock = pygame.time.Clock
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
run = False
if event.type == pygame.KEYDOWN:
#Under here is the game logic and i don't know if i should have the random enemy movement under here
if(event.key == pygame.K_UP):
self.Mycharacter.move_up()
elif(event.key == pygame.K_DOWN):
self.Mycharacter.move_down()
elif(event.key == pygame.K_LEFT):
self.Mycharacter.move_left()
elif(event.key == pygame.K_RIGHT):
self.Mycharacter.move_right()
self.score += 1
if self.score > 400:
if self.score > 800:
if self.score > 1200:
self.enemy.speed(18)
else:
self.enemy.speed(16)
else:
self.enemy.speed(14)
else:
self.enemy.speed(12)
self.mysprites.update()
#self.mysprites2.group_collide
#possibly sprite.groupcollide to save myself these two loops but I like the loops
#also idk if the dokill means it will .kill() or something else
for i in range(len(self.obstacles)):
if(pygame.sprite.collide_rect(self.Mycharacter, self.obstacle[i])):
self.Mycharacter.kill()
self.currentState = "end"
self.end_time = pygame.time.get_ticks()
run = False
for i in range(len(self.enemies)):
if(pygame.sprite.collide_rect(self.Mycharacter, self.enemies[i])):
self.Mycharacter.kill()
self.currentState = "end"
self.end_time = pygame.time.get_ticks()
run = False
self.screen.fill(GREEN)
#self.obstacles.draw(self.screen)
#self.enemies.draw(self.screen)
self.screen.blit(self.background, (0, 0))
self.mysprites.draw()
#self.screen.blit(self.Mycharacter,(self.Mycharacter.rect.x, self.Mycharacter.rect.y))
#drawing code
#update screen with what has been drawn
pygame.display.flip()
clock.tick(60)
pygame.quit
def endGame(self):
#self.screen = pygame.display.set_mode(800, 800)
#self.background = pygame.Surface(self.screen.get_size()).convert()
end = False
while not end:
for event in pygame.event.get():
if event == pygame.QUIT:
end = True
self.done = True
else:
line = json.load(self.file.readline())
num = line["one"]
self.file.close()
if self.time > int(num):
self.file = open("highscores.json", "w")
newstr = "highscore:"+str(num)
jsonstr = json.dump(newstr)
self.file.write(newstr)
self.file.close
self.file = open("highscores.json", "r")
newline = json.load(self.file.readline())
score = newline["highscore"]
self.file.close()
self.screen.fill((0, 0, 255))
#create text and rect to blit onto screen to display high score
self.screen.blit("Highscore: " + str(score), (350, 50))
self.screen.blit("Your Score: " + str(num), (350, 150))
pygame.display.flip()
pygame.quit
def main():
the_game = Controller()
the_game.mainLoop()
main()
The first problem that I can see is that you pass self as an argument to the different scene methods self.startGame(self) in the mainLoop. Just remove the self.
In the runGame method, you have indented a lot of your code incorrectly. The game logic and drawing code shouldn't be in the event loop but in the outer while loop. It should still draw everything, though, but only if events are in the queue (for example if the mouse gets moved).
Also, the self.background surface is just black because you never fill it with another color.
Side notes: You forgot some parentheses behind clock = pygame.time.Clock an pygame.quit.
In the endGame method, you load the json file every time an event occurs (f.e. mouse movements). You should better do that once per mouse click or something similar.
Here's a minimal example that works correctly:
import pygame
GREEN = pygame.Color('green')
class Controller:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((800, 800))
self.background = pygame.Surface(self.screen.get_size()).convert()
self.background.fill(GREEN) # Fill the background if it shouldn't be black.
self.clock = pygame.time.Clock()
self.currentState = "start"
self.button = pygame.Rect(350, 50, 100, 100)
def mainLoop(self):
self.done = False
while not self.done:
if self.currentState == "start":
self.startGame()
elif self.currentState == "running":
self.start_time = pygame.time.get_ticks()
self.runGame()
def startGame(self):
start = True
while start:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
start = False
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
if self.button.collidepoint(mouse_pos):
self.currentState = "running"
return # Back to the mainloop
self.screen.fill((255, 0, 255))
pygame.draw.rect(self.screen, (0, 0, 110), self.button)
pygame.display.flip()
self.clock.tick(60)
def runGame(self):
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
run = False
self.screen.blit(self.background, (0, 0))
pygame.draw.rect(self.screen, (200, 0, 0), [20, 30, 50, 90])
pygame.display.flip()
self.clock.tick(60)
def main():
the_game = Controller()
the_game.mainLoop()
main()
pygame.quit()

How do I create a button that changes sprites when clicked in pygame?

I am currently having trouble with buttons in pygame. At the moment I am testing myself in pygame and I am trying to create a flappy bird type of game. What I am trying to achieve is that when I click on the play button on the main menu it will change the button sprite and run the main game.
I have managed to define the button function and get it to switch to the main game when I click. The only problem is that it doesn't show a different sprite when clicked and I can click anywhere in the application to switch to the main game instead of having to click on the button.
Any help would be much appreciated.
Thanks in Advance
import time
import random
import pygame
from pygame.locals import *
pygame.init()
#Predefined Colors
white = (255,255,255)
black = (0,0,0)
red = (200,0,0)
light_red = (255,0,0)
yellow = (200,200,0)
light_yellow = (255,255,0)
green = (34,177,76)
light_green = (0,255,0)
blue = (0,0,255)
light_blue = (0, 0, 200)
player_list = (
# Red Bird
('assets/sprites/redbird-upflap.png', 'assets/sprites/redbird-midflap.png',
'assets/sprites/redbird-downflap.png'),
# Blue Bird
('assets/sprites/bluebird-upflap.png', 'assets/sprites/bluebird-midflap.png',
'assets/sprites/bluebird-downflap.png'),
# Yellow Bird
('assets/sprites/yellowbird-upflap.png', 'assets/sprites/yellowbird-midflap.png',
'assets/sprites/yellowbird-downflap.png')
)
background_list = (
('assets/sprites/background-day.png', 'assets/sprites/background-night.png')
)
pipe_list = (
('assets/sprites/pipe-green.png', 'assets/sprites/pipe-red.png')
)
FPS = 30
images, sounds = {}, {}
def main():
global base_x, base_y, clock, gameDisplay, display_height, display_width
display_width = 288
display_height = 512
base_x = 0
base_y = display_height * 0.79
clock = pygame.time.Clock()
gameDisplay = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption("Flappy Bird")
#Loading icon sprite
images['Icon'] = pygame.image.load('assets/sprites/yellowbird-midflap.png')
pygame.display.set_icon(images['Icon'])
#Loading all the Numbers sprites
images['Numbers'] = (
pygame.image.load('assets/sprites/0.png').convert_alpha(),
pygame.image.load('assets/sprites/1.png').convert_alpha(),
pygame.image.load('assets/sprites/2.png').convert_alpha(),
pygame.image.load('assets/sprites/3.png').convert_alpha(),
pygame.image.load('assets/sprites/4.png').convert_alpha(),
pygame.image.load('assets/sprites/5.png').convert_alpha(),
pygame.image.load('assets/sprites/6.png').convert_alpha(),
pygame.image.load('assets/sprites/7.png').convert_alpha(),
pygame.image.load('assets/sprites/8.png').convert_alpha(),
pygame.image.load('assets/sprites/9.png').convert_alpha()
)
#Game Over Sprite
images['Game Over'] = pygame.image.load('assets/sprites/gameover.png').convert_alpha()
#Starting Game sprite
images['Starting Game'] = pygame.image.load('assets/sprites/startgame-screen.png').convert_alpha()
#Flappy Bird Logo sprite
images['Flappy Bird Logo'] = pygame.image.load('assets/sprites/flappybird-logo.png').convert_alpha()
#Base Ground sprite
images['Base Ground'] = pygame.image.load('assets/sprites/base.png').convert_alpha()
#Play Button Up sprite
images['Play Button Up'] = pygame.image.load('assets/sprites/playbutton-up.png').convert_alpha()
#Play Button Down sprite
images['Play Button Down'] = pygame.image.load('assets/sprites/playbutton-down.png').convert_alpha()
#Quit Button Up sprite
#images['Quit Button Up'] = pygame.image.load('assets/sprites/quitbutton-up.png').convert_alpha()
#Quit Button Down sprite
#images['Quit Button Down'] = pygame.image.load('assets/sprites/quitbutton-down.png').convert_alpha()
#Sounds
# sounds['Die'] = pygame.mixer.Sound('assets/audio/die.wav')
# sounds['Hit'] = pygame.mixer.Sound('assets/audio/hit.wav')
# sounds['Point'] = pygame.mixer.Sound('assets/audio/point.wav')
# sounds['swoosh'] = pygame.mixer.Sound('assets/audio/swoosh.wav')
# sounds['wing'] = pygame.mixer.Sound('assets/audio/wing.wav')
while True:
#Select random Background sprites
random_background = random.randint(0, len(background_list) - 1)
images['Background'] = pygame.image.load(background_list[random_background]).convert()
#Select random Player sprites
random_player = random.randint(0, len(player_list) - 1)
images['Player'] = (
pygame.image.load(player_list[random_player][0]).convert_alpha(),
pygame.image.load(player_list[random_player][1]).convert_alpha(),
pygame.image.load(player_list[random_player][2]).convert_alpha()
)
#Select random Pipe sprite
random_pipe = random.randint(0, len(pipe_list) - 1)
images['Pipe'] = pygame.image.load(pipe_list[random_pipe])
main_menu()
pygame.display.update()
clock.tick(FPS)
def button(action = None):
cur = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if action == 'Play':
button = images['Play Button Up'].get_rect()
for event in pygame.event.get():
if click[0] == 1:
if button.collidepoint(cur):
print ('Mouse Over')
images['Play Button Down']
main_game()
else:
gameDisplay.blit(images['Play Button Up'], (0, -10))
def main_menu():
global player_index, player_x, player_y
player_index = 0
player_x = int(display_width * 0.2)
player_y = int((display_height - images['Player'] [0].get_height()) / 2)
menu = True
while menu:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
gameDisplay.blit(images['Flappy Bird Logo'], (50, -30))
gameDisplay.blit(images['Player'][player_index], (125, 140))
gameDisplay.blit(images['Play Button Up'], (10, 10))
button(action = 'Play')
pygame.display.update()
clock.tick(FPS)
def main_game():
gameExit = False
gameOver = False
player_x = 0
player_y = 0
while not gameExit:
if gameOver == True:
gameDisplay.blit(images['Game Over'], 50, 50)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Starting Game'], (0, 0))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
gameDisplay.blit(images['Player'][player_index], (player_x, player_y))
pygame.display.update()
clock.tick(FPS)
main()
Here's a minimal example to demonstrate how you can switch between different button images.
When the user presses a mouse button (a pygame.MOUSEBUTTONDOWN event is added to the queue), check if the event.pos collides with the button rect and if it collides, set the image to the "down" version.
When the user releases the button (pygame.MOUSEBUTTONUP event), just set the image back to the original version.
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
GRAY = pg.Color('gray15')
BLUE = pg.Color('dodgerblue1')
LIGHTBLUE = pg.Color('lightskyblue1')
BUTTON_UP_IMG = pg.Surface((50, 30))
BUTTON_UP_IMG.fill(BLUE)
BUTTON_DOWN_IMG = pg.Surface((50, 30))
BUTTON_DOWN_IMG.fill(LIGHTBLUE)
def main():
clock = pg.time.Clock()
font = pg.font.Font(None, 30)
# Currently selected button image.
button_image = BUTTON_UP_IMG
button_rect = button_image.get_rect(topleft=(200, 200))
x = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
if button_rect.collidepoint(event.pos):
button_image = BUTTON_DOWN_IMG
elif event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
button_image = BUTTON_UP_IMG
if button_rect.collidepoint(event.pos):
print('Button pressed.')
x += 1
screen.fill(GRAY)
screen.blit(button_image, button_rect)
txt = font.render(str(x), True, BLUE)
screen.blit(txt, (260, 206))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
I would actually recommend to use classes, pygame sprites and sprite groups instead of just rects and images. Then you can easily create as many instances of the button class as you want.
import pygame as pg
pg.init()
GRAY= pg.Color('gray12')
BLUE = pg.Color('dodgerblue1')
FONT = pg.font.Font(None, 30)
BUTTON_UP_IMG = pg.Surface((50, 30))
BUTTON_UP_IMG.fill(BLUE)
BUTTON_DOWN_IMG = pg.Surface((50, 30))
BUTTON_DOWN_IMG.fill(pg.Color('lightskyblue1'))
# The Button is a pygame sprite, that means we can add the
# instances to a sprite group and then update and render them
# by calling `sprite_group.update()` and `sprite_group.draw(screen)`.
class Button(pg.sprite.Sprite):
def __init__(self, pos, callback):
pg.sprite.Sprite.__init__(self)
self.image = BUTTON_UP_IMG
self.rect = self.image.get_rect(topleft=pos)
self.callback = callback
def handle_event(self, event):
"""Handle events that get passed from the event loop."""
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
if self.rect.collidepoint(event.pos):
self.image = BUTTON_DOWN_IMG
elif event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
self.image = BUTTON_UP_IMG
if self.rect.collidepoint(event.pos):
print('Button pressed.')
# Call the function that we passed during the
# instantiation. (In this case just `increase_x`.)
self.callback()
class Game:
def __init__(self):
self.screen = pg.display.set_mode((800, 600))
self.clock = pg.time.Clock()
self.x = 0
self.buttons = pg.sprite.Group(
Button((200, 200), callback=self.increase_x),
Button((500, 200), callback=self.decrease_x))
self.done = False
# A callback function that we pass to the button instance.
# It gets called if a collision in the handle_event method
# is detected.
def increase_x(self):
"""Increase self.x if button is pressed."""
self.x += 1
def decrease_x(self):
"""Decrease self.x if button is pressed."""
self.x -= 1
def run(self):
while not self.done:
self.handle_events()
self.run_logic()
self.draw()
self.clock.tick(30)
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
for button in self.buttons:
button.handle_event(event)
def run_logic(self):
self.buttons.update()
def draw(self):
self.screen.fill(GRAY)
self.buttons.draw(self.screen)
txt = FONT.render(str(self.x), True, BLUE)
self.screen.blit(txt, (360, 206))
pg.display.flip()
if __name__ == "__main__":
Game().run()
pg.quit()
Thanks for the Answer to my previous question your answer solved my previous issue, but now I have ran into another issue.
When I press my button on the main menu it changes sprites correctly and loads the main game correctly, but as soon as I move my mouse it switches back to the main menu. My best guess is that it is because it is looping the whole button sequence, but I am not entirely sure.
import pygame
pygame.init()
display_width = 288
display_height = 512
def main_menu():
done = False
play_button_image = images['Play Button Up']
play_button_rect = play_button_image.get_rect(topleft=(30,15))
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if play_button_rect.collidepoint(event.pos):
play_button_image = images['Play Button Down']
elif event.type == MOUSEBUTTONUP:
if event.button == 1:
play_button_image = images['Play Button Up']
if play_button_rect.collidepoint(event.pos):
main_game()
gameDisplay.blit(play_button_image, play_button_rect)
pygame.display.update()
clock.tick(FPS)
def main_game():
gameExit = False
gameOver = False
player_index = 0
player_x = int(display_width * 0.2)
player_y = int((display_height - images['Player'][0].get_height()) / 2)
player_index_gen = cycle([0, 1, 2, 1])
loop_iter = 0
starting_game_x = int((display_width - images['Starting Game'].get_width()) / 2)
starting_game_y = int(display_height * 0.12)
base_x = 0
base_shift = images['Base Ground'].get_width() - images['Background'].get_width()
player_move_vals = {'val': 0, 'dir': 1}
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN or pygame.K_SPACE or pygame.K_UP:
sounds['Wing']
return {
'player_y': player_y + player_move_vals['val'],
'base_x': base_x,
'player_index_gen': player_index_gen
}
if (loop_iter + 1) % 5 == 0:
player_index = next(player_index_gen)
loop_iter = (loop_iter + 1) % 30
base_x = -((-base_x + 4) % base_shift)
player_move(player_move_vals)
# draw sprites
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Player'][player_index],
(player_x, player_y + player_move_vals['val']))
gameDisplay.blit(images['Starting Game'], (starting_game_x, starting_game_y))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
pygame.display.update()
clock.tick(FPS)
if __name__ == '__main__':
main()

Understanding & making infinite bullets to shoot in Pygame

I'm learning python and also pygame, and I want to know why the bullets doesn't display, I tough that maybe the screen.update_screen() can be interfering but no, that's not the case, I need help to undertand how Bullets work in python because clearly my method is not working, I've seen many methods in other posts, and they use a limited ammount of bullets to shoot (don't know why, in my case I want infinite bullets) so what should I add to see the Bullets, I know that I need to add a remover for seing a "movement" in the display, but I don't know how, any help is appreciated.
# -*- coding: utf-8 -*-
import pygame
class Screen(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
pygame.display.set_caption("Space Game")
self.screen_width = 800
self.screen_heigh = 600
self.picture = pygame.image.load("screen.png")
self.screen = pygame.display.set_mode((self.screen_width, self.screen_heigh))
def update_screen(self):
self.screen.blit(self.picture, (0, 0))
def update_obj(self, object):
self.screen.blit(object.picture, object.rect)
def update_shoot(self, object):
for y in range(object.rect.centery, 600, 10):
self.screen.blit(object.picture, (object.rect.centerx, object.rect.centery + y))
class Ship(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.picture = pygame.image.load("ship.png")
self.rect = self.picture.get_rect()
self.rect.centerx = 400
self.rect.centery = 500
class Bullet(pygame.sprite.Sprite):
def __init__(self, object):
pygame.sprite.Sprite.__init__(self)
self.picture = pygame.image.load("shoot.png")
self.rect = self.picture.get_rect()
self.rect.centerx = object.rect.centerx
self.rect.centery = (object.rect.centery + 25)
def main():
pygame.init()
done = False
clock = pygame.time.Clock()
screen = Screen()
ship = Ship()
bullet = Bullet(ship)
while not done:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
ship.rect.centerx -= 5
if keys[pygame.K_RIGHT]:
ship.rect.centerx += 5
if keys[pygame.K_UP]:
ship.rect.centery -= 5
if keys[pygame.K_DOWN]:
ship.rect.centery += 5
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
done = True
if event.key == pygame.K_SPACE:
print ("shoot")
#for some weird reason the bullet doesn't display
screen.update_shoot(bullet)
screen.update_screen()
screen.update_obj(ship)
pygame.display.update()
clock.tick(10)
pygame.quit()
if __name__ == "__main__":
main()
To shoot bullets you usually create instances of a Bullet class and add them to a list, pygame.sprite.Group or another container. Then you iterate over this container and call the update method of the bullet in which its position is changed. To blit the images of the sprites/objects you iterate again over the container and just blit the images onto the screen. With sprite groups you can just call sprite_group.update() and sprite_group.draw(screen) instead of iterating yourself. BTW, pygame sprites have to have a self.image attribute not a self.picture in order to work with sprite groups (take a look at Program Arcade Games for more information).
I started to modify a few things in your example to show you how to use sprite groups, but then ended up changing your whole Screen class into a Game class (which I recommend to use in the future).
import sys
import pygame
class Game:
def __init__(self):
self.done = False
self.screen_width = 800
self.screen_height = 600
self.image = pygame.Surface((800, 600))
self.image.fill((30, 40, 50))
self.screen = pygame.display.set_mode(
(self.screen_width, self.screen_height))
# all_sprites is used to update and draw all sprites together.
self.all_sprites = pygame.sprite.Group()
# You'll probably need a separate bullet_group
# later for collision detection with enemies.
self.bullet_group = pygame.sprite.Group()
self.ship = Ship()
self.all_sprites.add(self.ship)
bullet = Bullet(self.ship)
self.bullet_group.add(bullet)
self.all_sprites.add(bullet)
def handle_events(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.ship.rect.centerx -= 5
if keys[pygame.K_RIGHT]:
self.ship.rect.centerx += 5
if keys[pygame.K_UP]:
self.ship.rect.centery -= 5
if keys[pygame.K_DOWN]:
self.ship.rect.centery += 5
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.done = True
if event.key == pygame.K_SPACE:
bullet = Bullet(self.ship)
self.bullet_group.add(bullet)
self.all_sprites.add(bullet)
def update(self):
# Calls `update` methods of all contained sprites.
self.all_sprites.update()
def draw(self):
self.screen.blit(self.image, (0, 0))
self.all_sprites.draw(self.screen) # Draw the contained sprites.
pygame.display.update()
class Ship(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((20, 30))
self.image.fill((50, 170, 230))
# A nicer way to set the start pos with `get_rect`.
self.rect = self.image.get_rect(center=(400, 500))
class Bullet(pygame.sprite.Sprite):
def __init__(self, ship):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((7, 7))
self.image.fill((230, 140, 30))
self.rect = self.image.get_rect()
self.rect.centerx = ship.rect.centerx
self.rect.centery = ship.rect.centery - 25
def update(self):
self.rect.y -= 5 # Move up 5 pixels per frame.
def main():
pygame.init()
pygame.display.set_caption('Space Game')
clock = pygame.time.Clock()
game = Game()
while not game.done:
game.handle_events()
game.update()
game.draw()
clock.tick(30)
if __name__ == '__main__':
main()
pygame.quit()
sys.exit()

Pygame get scroll coordinates

Is there any way to get the coordinates of the pygame surface "scroll" function?
e.g.
image.scroll(0,32)
scroll_coords = image.??? ### scroll_coords should be (0,32)
You could just store the scroll coords in a vector, list or rect, and whenever you scroll the surface, update the vector as well. (Press w or s to scroll the surface)
import sys
import pygame as pg
def main():
clock = pg.time.Clock()
screen = pg.display.set_mode((640, 480))
image = pg.Surface((300, 300))
image.fill((20, 100, 90))
for i in range(10):
pg.draw.rect(image, (160, 190, 120), (40*i, 30*i, 30, 30))
scroll_coords = pg.math.Vector2(0, 0)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if event.type == pg.KEYDOWN:
if event.key == pg.K_w:
scroll_coords.y -= 10
image.scroll(0, -10)
elif event.key == pg.K_s:
scroll_coords.y += 10
image.scroll(0, 10)
print(scroll_coords)
screen.fill((50, 50, 50))
screen.blit(image, (100, 100))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()

Resources