Related
I'm working on a subprogram code that will make this happy face bounce around the screen and turn different colours. For some reason, the screen turns into that black glitchy screen and when I press exit at the top the face shows for a quick second before the program shuts down. I can't figure out why this is, here is my code and I've included a picture of what happens at first when I run it:
""" Program to show a very basic function
Most of the program is exactly the same as other programs we have done
The main difference is the grouping of code into a function called
drawHappy() to draw a few shapes together
In the main loop we "call" this function whenever we want to draw this
group of shapes
"""
# import the necessary modules
import pygame
import sys
import math
import random
from random import randint
# initialize pygame
pygame.init()
# set the size for the surface (screen)
# note this screen is resizable by the user
screen = pygame.display.set_mode((800, 600), pygame.RESIZABLE)
# set the caption for the screen
pygame.display.set_caption("Happy Face")
#screen width and height
screenW = screen.get_width()
screenH = screen.get_height()
# define colours you will be using
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
YELLOW = (255, 255, 0)
# funtion to draw a the "happy face"
# it has 4 parameters passed to it xPos, yPos, radius, and colour
# notice all the shapes are drawn "relative" to the xPos and yPos and the radius
def drawHappy(xPos,yPos,r,colour):
pygame.draw.circle(screen,colour,(xPos,yPos),r,1)
eyeRadius = int(1/6*r)
eyeX = int(xPos-1/3*r)
eyeY = int(yPos- 1/3*r)
pygame.draw.circle(screen,colour,(eyeX,eyeY),eyeRadius,1)
eyeX = int(xPos + 1/3*r)
pygame.draw.circle(screen,colour,(eyeX,eyeY),eyeRadius,1)
wMouth = 1.5*r
xMouth = xPos - 3/4*r
yMouth = yPos - 3/4*r
pygame.draw.arc(screen,colour,(xMouth,yMouth,wMouth,wMouth),math.pi,2*math.pi,1)
randomR = randint(1,300)
r = randomR
randomX = randint(r, 800-r)
randomY = randint(r, 600-r)
dx = 0
dy = 0
x = 100
y = 100
speed = 3
x2 = randomX
y2 = randomY
dx2 = speed
dy2 = -speed
colour_list = [YELLOW, BLACK, BLUE, RED, GREEN]
randomcolour = random.choice(colour_list)
colour = RED
# set up clock to control frames per second
clock = pygame.time.Clock()
FPS = 120
# set main loop to True so it will run
main = True
# main loop
while main:
for event in pygame.event.get(): # check for any events (i.e key press, mouse click etc.)
if event.type == pygame.QUIT: # check to see if it was "x" at top right of screen
main = False # set the "main" variable to False to exit while loop
clock.tick(FPS)
screen.fill(WHITE)
oldx = x
oldy = y
x += dx
y += dy
if x >= 800-r or x <= 0+r:
x = oldx
if y >= 600-r or y <= 0+r:
y = oldy
x2 += dx2
y2 += dy2
if x >= 800-r or x <= 0+r:
dx2 = -dx2
randomcolour = random.choice(colour_list)
colour = randomcolour
if y2 >= 600-r or y2 <= 0+r:
dy2 = -dy2
randomcolour = random.choice(colour_list)
colour = randomcolour
# "call" the function "drawHappy()" to draw the happy face
# this is where we would normally do a pygame.draw or a screen.blit()
# we are "passing" the function 4 values to use(x,y,radius, colour)
# it will use these to know where to draw the happy face
drawHappy(x2,y2,r,colour)
pygame.display.flip()
# quit pygame and exit the program (i.e. close everything down)
pygame.quit()
sys.exit()
First of all, you need to call your draw function inside the loop. Your current code shows only a glimpse of "drawing" because it gets executed once you exit the main loop.
So, put your drawHappy() inside of main loop:
while main:
for event in pygame.event.get(): # check for any events (i.e key press, mouse click etc.)
if event.type == pygame.QUIT: # check to see if it was "x" at top right of screen
main = False # set the "main" variable to False to exit while loop
drawHappy(x2,y2,r,colour)
pygame.display.update()
clock.tick(FPS)
screen.fill(WHITE)
Now you will get a random size "smiley" on the screen, But now it will move on exit only, for the same reason it wouldn't display earlier. Next thing is to make it bounce (move). For this you'll need some kind of update of the coordinates, just like you did in the last part of your code, except they also need to be updated during the loop, not after it.
I suggest making a Class because then it will be easier to manipulate the object.
Also, I found it easier to separate draw and update_coordinates code into separate functions and them call them from main loop for example.
Hope this helps, and if you need more help, ask.
Here, I made a quick solution using parts of your code, there is plenty room for improvement especially for update_smiley_position() method where you can control how "smiley" moves.
Also, if you need multiple objects, a list should be passed instead of single object.
import pygame as pg
import math
import random
pg.init()
clock = pg.time.Clock()
window = pg.display.set_mode((800, 600), pg.RESIZABLE)
pg.display.set_caption("Happy Face")
SCREEN_W = window.get_width()
SCREEN_H = window.get_height()
class Smiley:
def __init__(self, x, y, r, color):
self.x = x
self.y = y
self.r = r
self.color = color
self.create_smiley()
def create_smiley(self):
self.eye_radius = int(1/6 * self.r)
self.eye_x1 = int(self.x - 1/3 * self.r)
self.eye_x2 = int(self.x + 1/3 *self.r)
self.eye_y = int(self.y - 1/3 *self.r)
self.mouth_width = 1.5 * self.r
self.mouth_x = self.x - self.r * 0.75
self.mouth_y = self.y - self.r * 0.75
def draw_smiley(self, win):
pg.draw.circle(win, self.color, (self.x, self.y), self.r, 1)
pg.draw.circle(win, self.color, (self.eye_x1, self.eye_y), self.eye_radius, 1)
pg.draw.circle(win, self.color, (self.eye_x2, self.eye_y), self.eye_radius, 1)
pg.draw.arc(win, self.color, (self.mouth_x, self.mouth_y, self.mouth_width, self.mouth_width), math.pi, 2*math.pi, 1)
def update_smiley_position(self):
if self.x >= SCREEN_H - self.r or self.x <= 0 + self.r:
self.x = random.randint(100, 400)
else:
self.x += 5
if self.y >= SCREEN_W - self.r or self.y <= 0 + self.r:
self.y = random.randint(100, 400)
else:
self.y -= 5
self.create_smiley()
def draw(win, smiley):
win.fill(pg.Color("white"))
smiley.draw_smiley(win)
smiley.update_smiley_position()
pg.display.update()
def main_loop(win, smiley):
clock.tick(30)
for event in pg.event.get():
if event.type == pg.QUIT:
return False
draw(win, smiley)
return True
r = random.randint(1, 300)
x = random.randint(r, SCREEN_W - r)
y = random.randint(r, SCREEN_H - r)
smiley = Smiley(x, y, r, pg.Color("red"))
while main_loop(window, smiley):
pass
pg.quit()
Part of an assignment I'm working on is making a ball bounce around the screen, I can make it move, but my boundary test doesn't seem to be working: the ball simply moves in direction instead of changing direction. So to clarify, what I want to ball to do is change direction as it hits the screen edge.
import sys
import pygame
SCREEN_SIZE = 750, 550
BALL_DIAMETER = 16
BALL_RADIUS = BALL_DIAMETER // 2
MAX_BALL_X = SCREEN_SIZE[0] - BALL_DIAMETER
MAX_BALL_Y = SCREEN_SIZE[1] - BALL_DIAMETER
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
LEFT = 11
RIGHT = 12
pygame.init()
clock = pygame.time.Clock()
pygame.display.init()
font = pygame.font.SysFont("impact", 20)
pygame.display.set_caption("Breakout")
screen = pygame.display.set_mode(SCREEN_SIZE)
class Ball:
def __init__(self):
''' '''
self.ball = pygame.Rect(300, 730 -
BALL_DIAMETER,
BALL_DIAMETER, BALL_DIAMETER)
# Draw ball
def draw_ball(self):
pygame.draw.circle(screen,
WHITE, (self.ball.left
+ BALL_RADIUS, self.ball.top +
BALL_RADIUS), BALL_RADIUS)
# Updates the coordinates by adding the speed components
def move_ball(self, x, y):
self.xspeed = x
self.yspeed = y
self.ball = self.ball.move(self.xspeed, self.yspeed)
# bounds check
if self.ball.left <= 0:
self.ball.left = 0
self.xspeed = -self.xspeed
elif self.ball.left >= MAX_BALL_X:
self.ball.left = MAX_BALL_X
self.xspeed = -self.xspeed
if self.ball.top < 0:
self.ball.top = 0
self.yspeed = -self.yspeed
elif self.ball.top >= MAX_BALL_Y:
self.ball.top = MAX_BALL_Y
self.yspeed = -self.yspeed
# shows a message on screen, for testing purposes
class Text:
def show_message(self, message):
self.font = pygame.font.SysFont("impact", 20)
font = self.font.render(message,False, WHITE)
screen.blit(font, (200, 400))
class Game:
def __init__(self):
''' '''
def run(self):
b = Ball()
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
keys = pygame.key.get_pressed()
# fps lock, screen fill and method call for input
clock.tick(60)
screen.fill(BLACK)
b.draw_ball()
b.move_ball(5, -5)
# used to keep track of various elements
# Text().show_message("P: " + str(p))
pygame.display.flip()
# Creates instance of the game class, and runs it
if __name__ == "__main__":
Game().run()
Your only call to move_ball uses a constant vector.
Since you never change the call parameters, the ball moves only that way.
b.move_ball(5, -5)
Yes, you change the vector components within move_ball when you hit a wall. However, on the next call, you change them back to the original values and move the ball in the original direction.
You have to initialize the vector outside move_ball, and then let the routine access the existing vector when it's called.
Im very new to python and seem to be missing something.
I want to randomly draw circles on a pygame display but only if the circles don't overlap each other.
I believe I must find the distance between all circle centers and only draw it if the distance is bigger than circle radius * 2.
I've tried many different things but all without success, I always get the same result - circles drawn overlapping.
#!/usr/bin/env python
import pygame, random, math
red = (255, 0, 0)
width = 800
height = 600
circle_num = 10
tick = 2
speed = 5
pygame.init()
screen = pygame.display.set_mode((width, height))
class circle():
def __init__(self):
self.x = random.randint(0,width)
self.y = random.randint(0,height)
self.r = 100
def new(self):
pygame.draw.circle(screen, red, (self.x,self.y), self.r, tick)
c = []
for i in range(circle_num):
c.append('c'+str(i))
c[i] = circle()
for j in range(len(c)):
dist = int(math.hypot(c[i].x - c[j].x, c[i].y - c[j].y))
if dist > int(c[i].r*2 + c[j].r*2):
c[j].new()
pygame.display.update()
else:
continue
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
You did not check against all other circles. I added a variable shouldprint which gets set to false if any other circle is too close.
import pygame, random, math
red = (255, 0, 0)
width = 800
height = 600
circle_num = 20
tick = 2
speed = 5
pygame.init()
screen = pygame.display.set_mode((width, height))
class circle():
def __init__(self):
self.x = random.randint(0,width)
self.y = random.randint(0,height)
self.r = 100
def new(self):
pygame.draw.circle(screen, red, (self.x,self.y), self.r, tick)
c = []
for i in range(circle_num):
c.append('c'+str(i))
c[i] = circle()
shouldprint = True
for j in range(len(c)):
if i != j:
dist = int(math.hypot(c[i].x - c[j].x, c[i].y - c[j].y))
if dist < int(c[i].r*2):
shouldprint = False
if shouldprint:
c[i].new()
pygame.display.update()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
The for loop has been changed to a while loop. It will keep trying to generate circles until the target number is reached. A circle is first generated. Then, it checks if it intersects with any existing circle using the formula from this answer.
It iterates through every existing circle (store in the list circles) and performs the check using the formula. any() returns True if the formula evaluates to True for any iteration. If it's True, it means it found an intersection. Thus, it continues to the next iteration to try again with a new circle.
circles = []
while len(circles) < circle_num:
new = circle()
if any(pow(c.r - new.r, 2) <=
pow(c.x - new.x, 2) + pow(c.y - new.y, 2) <=
pow(c.r + new.r, 2)
for c in circles):
continue
circles.append(new)
new.new()
pygame.display.update()
I'm wondering how to speed up the smoothness of my code written in Python using pygam. I'm guessing I have to make this more efficient somehow? When this is run, some balls move around randomly in a set area, however, the new position of each ball is not smooth at all, there is a jump between each movement as the cycle is very slow. How do I fix this? Or is there any suggestions on how to improve it?
This is my code so far:
import pygame
from pygame import *
import random
pygame.init()
size = width, height = 800, 600
screen = display.set_mode(size)
pygame.display.set_caption("Year 12: Ideal Gas Simulation")
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
WHITE=(255,255,255)
GREEN = (0, 255, 0)
BALLX = 0
BALLY = 1
BALLSPEEDX = 2
BALLSPEEDY = 3
List=[]
radius=5
running=True
myClock=time.Clock()
myClock.tick(60)
def initBall():
for n in range(40):
ballx = random.randint(0, 800) # randomly setting the x position
bally = random.randint(0, 600) # randomly setting the y position
dirx = random.randint(-5,5) # randomly setting the x speed
diry = random.randint(-5,5) # randomly setting the y speed
data=[ballx, bally, dirx, diry]
List.append(data)
# returning a list with all the data the ball needs
return List # returning the list
def drawScreen(List):
draw.rect(screen, WHITE, (0, 0, 800, 600))
for x in range(40):
BALLX=List[x][0]
BALLY=List[x][1]
draw.circle(screen, GREEN, (BALLX,BALLY),radius)
display.flip()
pygame.draw.rect(screen, BLACK, (100-radius,100-radius,600+(2*radius),400+(2*radius)), 1)
f=pygame.font.SysFont(None,60)
text=f.render("PV=nRT",True,(0,0,0))
screen.blit(text,(300,height/20))
def moveBall(List):
for x in range(40):
BALLX=List[x][0]
BALLY=List[x][1]
SPEEDX=List[x][2]#####data[BALLX]== the first index of each list [x][0]
SPEEDY=List[x][3]##data[BALLSPEEDX]= List[x][2]
age=SPEEDX+BALLX
List[x][0]=age
# increases the position of the ball
plus=SPEEDY+BALLY
List[x][1]=plus
# checks to see if the ball is hitting the walls in the x direction
if BALLX > 700:
List[x][0] = 700#NORMALLY 800
third=List[x][2]
answer=third*-1
List[x][2]=answer
elif BALLX < 100:#NORMALLY 0
List[x][0] = 100
third=List[x][2]
answer=third*-1
List[x][2]=answer
# checks to see if the ball is hitting the walls in the y direction
if BALLY < 100:
List[x][1] = 100#NORMALLY 0
third=List[x][3]
answer=third*-1
List[x][3]=answer
elif BALLY > 500:
List[x][1] = 500#NORMALLY 600
third=List[x][3]
answer=third*-1
List[x][3]=answer
return List#return updated list
List=initBall()
while running==True:
for evnt in event.get():
if evnt.type==QUIT:
running=False
quit()
if evnt.type==MOUSEBUTTONDOWN:
mx,my=evnt.pos
button=evnt.button
drawScreen(List)
List=moveBall(List)
In addition to skrx's answer, you can also refactor the code and avoid a lot of duplicate calls. Also, indexing the BALLS array directly might improve performance slightly.
Generally, avoid naming variables inside functions with uppercase. These names are typically given to constants defined at the top of your file.
The version I came up with is below:
import array
import pygame
pygame.init()
import random
from pygame import *
size = WIDTH, HEIGHT = 800, 600
screen = display.set_mode(size)
pygame.display.set_caption("Year 12: Ideal Gas Simulation")
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
WHITE = (255,255,255)
GREEN = (0, 255, 0)
BALLX = 0
BALLY = 1
BALLSPEEDX = 2
BALLSPEEDY = 3
RADIUS = 5
BALLS = []
myClock = time.Clock()
myClock.tick(60)
def initBalls():
for n in range(40):
props = array.array('i', [
random.randint(0, WIDTH),
random.randint(0, HEIGHT),
random.randint(-5, 5),
random.randint(-5, 5),
])
BALLS.append(props)
def drawScreen():
draw.rect(screen, WHITE, (0, 0, 800, 600))
props = (100-RADIUS, 100-RADIUS, 600+(2*RADIUS), 400+(2*RADIUS))
pygame.draw.rect(screen, BLACK, props, 1)
f = pygame.font.SysFont(None, 60)
text = f.render("PV=nRT", True,(0, 0, 0))
screen.blit(text,(300, HEIGHT / 20))
for i in range(len(BALLS)):
draw.circle(screen, GREEN, BALLS[i][:2],RADIUS)
display.flip()
def moveBalls():
for i in range(len(BALLS)):
if BALLS[i][0] > 700:
BALLS[i][0] = 700
BALLS[i][2] *= -1
elif BALLS[i][0] < 100:
BALLS[i][0] = 100
BALLS[i][2] *= -1
else:
BALLS[i][0] += BALLS[i][2]
if BALLS[i][1] < 100:
BALLS[i][1] = 100
BALLS[i][3] *= -1
elif BALLS[i][1] > 500:
BALLS[i][1] = 500
BALLS[i][3] *= -1
else:
BALLS[i][1] += BALLS[i][3]
def main():
initBalls()
while True:
for evnt in event.get():
if evnt.type == QUIT:
pygame.quit()
return
elif evnt.type == MOUSEBUTTONDOWN:
mx, my = evnt.pos
button = evnt.button
drawScreen()
moveBalls()
if __name__ == "__main__":
main()
Call pygame.display.flip() only once per frame.
def drawScreen(List):
draw.rect(screen, WHITE, (0, 0, 800, 600))
for x in range(40):
BALLX=List[x][0]
BALLY=List[x][1]
draw.circle(screen, GREEN, (BALLX,BALLY),radius)
# display.flip() # Don't call `display.flip()` here.
pygame.draw.rect(screen, BLACK, (100-radius,100-radius,600+(2*radius),400+(2*radius)), 1)
screen.blit(text,(300,height/20))
pygame.display.flip() # Call it here.
I also recommend to use a pygame.time.Clock to limit the frame rate.
# Define the font object as a global constant.
FONT = pygame.font.SysFont(None, 60)
# If the text doesn't change you can also define it here.
TEXT = FONT.render("PV=nRT", True, (0,0,0))
# Instantiate a clock to limit the frame rate.
clock = pygame.time.Clock()
running = True
while running: # `== True` is not needed.
for evnt in event.get():
if evnt.type == QUIT:
running = False
# Better use `pygame.quit` and `sys.exit` to quit.
pygame.quit()
sys.exit()
drawScreen(List)
List = moveBall(List)
clock.tick(30) # Limit frame rate to 30 fps.
This question is really difficult to ask, but I know you guys here at Stack Overflow are the brightest minds.
I'm totally blinded by why this issue happens (I'm fairly at Python and Pygame, so any suggestions on how to improve the code will be received with the love of improving my skills).
What I'm creating:
It's really a gimmick project, I have a little 2.5" screen (PiTFT) attached to a Raspberry Pi and the code is creating a typewriter effect with a moving cursor in front of the text as it's being written.
Challenge 1 was that every time you move a sprite in pygame, you must redraw everything, otherwise you will see a trail, and since the cursor is moving in front of the text, the result would look like this:
I managed to solve this issue by blackening / clearing the screen. But then I lost all the previously written letters.
So I created a list (entireword), which I'm populing with all the previously written characters. I use this list every time I cycle through the loop to redraw all the previous written text.
So now:
As you can see, the text looks funny.
It's supposed to read:
[i] Initializing ...
[i] Entering ghost mode ... []
I've been spending hours and hours getting to this point - and the code ALMOST works perfectly! The magic happens in the function print_screen(), but WHAT in my code is causing the text to include a letter from the other line in the end? :>
Help is GREATLY appreciated <3
Here's the entire code:
import pygame
import time
import os
import sys
from time import sleep
from pygame.locals import *
positionx = 10
positiony = 10
entireword = []
entireword_pos = 10
counter = 0
entire_newline = False
#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
speed = 0.05
#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
myfont = pygame.font.SysFont("monospace", 18)
#Class
class cursors(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill((0,255,0))
self.rect = self.image.get_rect()
self.rect.center = (positionx + 10, positiony + 10)
def update(self):
self.rect.x = positionx + 10
self.rect.y = positiony
#Functions
#Prints to the screen
def print_screen(words, speed):
rel_speed = speed
for char in words:
#speed of writing
if char == ".":
sleep(0.3)
else:
sleep(rel_speed)
#re-renders previous written letters
global entireword
# Old Typewriter functionality - Changes position of cursor and text a newline
#Makes sure the previous letters are rendered and not lost
#xx is a delimter so the program can see when to make a newline and ofcourse ignore writing the delimiter
entireword.append(char)
if counter > 0:
loopcount = 1
linecount = 0 # This is to which line we are on
for prev in entireword:
if prev == 'xx':
global linecount
global positiony
global loopcount
linecount = linecount + 1
positiony = 17 * linecount
loopcount = 1
if prev != 'xx': #ignore writing the delimiter
pchar = myfont.render(prev, 1, (255,255,0))
screen.blit(pchar, (loopcount * 10, positiony))
loopcount = loopcount + 1
if char != 'xx':
# render text
letter = myfont.render(char, 1, (255,255,0))
#blits the latest letter to the screen
screen.blit(letter, (positionx, positiony))
# Appends xx as a delimiter to indicate a new line
if entire_newline == True:
entireword.append('xx')
global entire_newline
entire_newline = False
global positionx
positionx = positionx + 10
all_sprites.update()
all_sprites.draw(screen)
pygame.display.flip()
screen.fill((0,0,0)) # blackens / clears the screen
global counter
counter = counter + 1
#Positions cursor at new line
def newline():
global positionx
global positiony
positionx = 10
positiony = positiony + 17
all_sprites = pygame.sprite.Group()
cursor = cursors()
all_sprites.add(cursor)
#Main loop
running = True
while running:
global speed
global entire_newline
words = "[i] Initializing ..."
entire_newline = True
newline()
print_screen(words,speed)
words = "[i] Entering ghost mode ..."
entire_newline = True
newline()
print_screen(words,speed)
#Stops the endless loop if False
running = False
sleep(10)
Sorry if I don't answer your question directly, because your code is too confusing for me now, so I took the liberty to rewrite your code to get done what you want.
The idea is to have two sprites:
the cursor, which is a) displayed on the screen and b) keeps track of what text to write and where
the board, which is basically just a surface that the text is rendered on
Note how all the writing logic is on the Cursor class, and we have a nice, simple and dumb main loop.
import pygame
import os
#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
clock = pygame.time.Clock()
#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
class Board(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((WIDTH, HEIGHT))
self.image.fill((13,13,13))
self.image.set_colorkey((13,13,13))
self.rect = self.image.get_rect()
self.font = pygame.font.SysFont("monospace", 18)
def add(self, letter, pos):
s = self.font.render(letter, 1, (255, 255, 0))
self.image.blit(s, pos)
class Cursor(pygame.sprite.Sprite):
def __init__(self, board):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill((0,255,0))
self.text_height = 17
self.text_width = 10
self.rect = self.image.get_rect(topleft=(self.text_width, self.text_height))
self.board = board
self.text = ''
self.cooldown = 0
self.cooldowns = {'.': 12,
'[': 18,
']': 18,
' ': 5,
'\n': 30}
def write(self, text):
self.text = list(text)
def update(self):
if not self.cooldown and self.text:
letter = self.text.pop(0)
if letter == '\n':
self.rect.move_ip((0, self.text_height))
self.rect.x = self.text_width
else:
self.board.add(letter, self.rect.topleft)
self.rect.move_ip((self.text_width, 0))
self.cooldown = self.cooldowns.get(letter, 8)
if self.cooldown:
self.cooldown -= 1
all_sprites = pygame.sprite.Group()
board = Board()
cursor = Cursor(board)
all_sprites.add(cursor, board)
text = """[i] Initializing ...
[i] Entering ghost mode ...
done ...
"""
cursor.write(text)
#Main loop
running = True
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
all_sprites.update()
screen.fill((0, 0, 0))
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(60)
Use the pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. The time has to be set in milliseconds. e.g.:
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
Add a new letter to the text, when the timer event occurs:
while run:
for event in pygame.event.get():
# [...]
if event.type == typewriter_event:
text_len += 1
See also Typewriter
Minimal example:
repl.it/#Rabbid76/PyGame-Typewriter
import pygame
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (32, 32, 32), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
text = 'Hello World'
text_len = 0
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
text_surf = None
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == typewriter_event:
text_len += 1
if text_len > len(text):
text_len = 0
text_surf = None if text_len == 0 else font.render(text[:text_len], True, (255, 255, 128))
window.blit(background, (0, 0))
if text_surf:
window.blit(text_surf, text_surf.get_rect(midleft = window.get_rect().midleft).move(40, 0))
pygame.display.flip()
pygame.quit()
exit()