This question already has answers here:
How do I create animated sprites using Sprite Sheets in Pygame?
(1 answer)
How do I use Sprite Sheets in Pygame?
(1 answer)
Closed 1 year ago.
I am making a simple program that displays a background image and animates a simple sprite with only 3 frames. Every time I run it, it gives me the error
screen.blit(firstexp[currentimage], (xpos,ypos)) IndexError: list
index out of range
I've been working with this for a while now and I'm not sure what to do anymore. Any help would be greatly appreciated, here's my code:
import pygame, sys, random
from pygame.locals import *
pygame.init()
class Cat(pygame.sprite.Sprite):
animationArray = []
numframes = 0
currentframe = 0
anispeed = 0
lastdraw = 0
xspeed = 5
xpos = 10
ypos = 10
def __init__(self, imgname, startx, starty, w, h, numframes, anispeed):
pygame.sprite.Sprite.__init__(self)
spsheet = pygame.image.load(imgname)
f1 = spsheet.subsurface(0,0,120,60)
self.animationArray.append(f1)
f2 = spsheet.subsurface(0,60,120,60)
self.animationArray.append(f2)
f3 = spsheet.subsurface(0,120,120,60)
self.animationArray.append(f3)
self.numframes = 3
self.currentframe = 0
self.anispeed = anispeed
def update(self, secs):
self.xpos = self.xpos + self.xspeed
self.lastdraw = self.lastdraw+secs
if self.lastdraw self.anispeed:
self.currentframe = self.currentframe+1
if self.currentframe==2:
self.currentframe=0
self.lastdraw = 0
self.image = self.animationArray[self.currentframe]
self.rect = (self.xpos,0,0,120,180)
def setEnv(background):
clock = pygame.time.Clock()
backimage = pygame.image.load(background)
w = backimage.get_rect().w
h = backimage.get_rect().h
screen = pygame.display.set_mode((w,h))
return clock, backimage, screen
###main game code
clock, backimage, screen = setEnv("landscape-illustration.jpg")
allSprites = pygame.sprite.Group() collSprites = pygame.sprite.Group()
catx = Cat("runningcat.png", 0,0,120,180,3,1)
allSprites.add(catx)
collSprites.add(catx)
screen.blit(backimage,(0,0))
while True:
secs = clock.tick(30)/1000
for event in pygame.event.get():
if event.type==pygame.QUIT:
sys.exit()
allSprites.clear(screen, backimage)
allSprites.update(secs)
allSprites.draw(screen)
collSprites.draw(screen)
pygame.display.flip()
You need to change the update() method of Cat class. Try something like this :
def update(self, secs):
self.xpos += self.xspeed
self.lastdraw += secs
if self.lastdraw > self.anispeed:
self.currentframe = (self.currentframe+1) % 3
self.lastdraw = 0
self.image = self.animationArray[self.currentframe]
self.rect = (self.xpos,0,0,120,180)
if you want to know what is problem with your code, you can print value of cat.currentframe.
Related
This code is supposed to animate a sprite on a background, but it is just showing the sprite without any movement. I spend a day trying to figure out the issue, traced the code (I am novice so I might overlooked something), compared the code with the original author code line by line with no result, btw the original code runs smoothly meaning that it is not a problem in my PC.
Could you help me please
import os
import random
import math
import pygame
from os import listdir
from os.path import isfile, join
pygame.init()
pygame.display.set_caption("Platformer") #set the caption at the top of the window
BG_COLOR = (255,255,255) #White background, dont need it anymore
WIDTH, HEIGHT = 1000, 640 #screen dimensions
FPS = 60
PLAYER_VEL = 5 # the speed of the player
window = pygame.display.set_mode((WIDTH, HEIGHT)) #set the window with sizes
def flip(sprites):
return [pygame.transform.flip(sprite, True, False) for sprite in sprites]
def load_sprite_sheets(dir1, dir2, width, height, direction=False):
path = join("assets", dir1, dir2)
images = [f for f in listdir(path) if isfile(join(path, f))] #if f is a file put in the images list
all_sprites = {}
for image in images:
sprite_sheet = pygame.image.load(join(path, image)).convert_alpha() #loaded transparent bg image
sprites = []
for i in range(sprite_sheet.get_width() // width):
surface = pygame.Surface((width, height), pygame.SRCALPHA, 32)
rect = pygame.Rect(i * width, 0, width, height)
surface.blit(sprite_sheet, (0, 0), rect)
sprites.append(pygame.transform.scale2x(surface))
if direction:
all_sprites[image.replace(".png", "") + "_right"] = sprites
all_sprites[image.replace(".png", "") + "_left"] = flip(sprites)
else:
all_sprites[image.replace(".png", "")] = sprites
return all_sprites
class Player(pygame.sprite.Sprite): #sprite is useful for perfect pixel collision
COLOR = (0,0,255)
GRAVITY = 1
SPRITES = load_sprite_sheets("MainCharacters" , "MaskDude", 32 , 32 , True)
ANIMATION_DELAY = 3
def __init__(self, x,y, width , height):
self.rect = pygame.Rect(x,y,width,height)
self.x_vel = 0
self.y_vel = 0
self.mask = None
self.direction = "left" # to record which animation to show
self.animation_count = 0 #
self.fall_count = 0
def move(self,dx,dy):
self.rect.x += dx
self.rect.y += dy #here we draw only the motion calculated in the next functions
def move_left(self, vel):
self.x_vel = -vel
if self.direction != "left":
self.direction = "left"
self.animation_count = 0
def move_right(self, vel):
self.x_vel = vel
if self.direction != "right":
self.direction = "right"
self.animation_count = 0
def loop(self , fps):
# self.y_vel += min(1 , (self.fall_count/fps) * self.GRAVITY)
self.move(self.x_vel,self.y_vel)
self.fall_count += 1
self.update_sprite()
def update_sprite(self): #is about changing the sprite shape to look walking
sprite_sheet = "idle"
if self.x_vel !=0:
sprite_sheet = "run"
sprite_sheet_name = sprite_sheet + "_" + self.direction
sprites = self.SPRITES[sprite_sheet_name]
sprite_index = (self.animation_count //
self.ANIMATION_DELAY) % len(sprites) #set new index every ANIMATION_DELAY = 5
self.sprite = sprites[sprite_index]
self.animation_count += 1
def draw(self, win):
#print(self.SPRITES)
self.sprite = self.SPRITES["idle_"+self.direction][0]
win.blit(self.sprite , (self.rect.x , self.rect.y))
def get_background(name): #name is bg image, this create the bg image position list
image = pygame.image.load(join("assets", "Background", name))
_, _, width, height = image.get_rect()
tiles = [] #list of tles I need to fil my window bg
for i in range(WIDTH//width + 1):
for j in range (HEIGHT // height+1):
pos = tuple([i * width , j*height]) # convert list into tuple
tiles.append(pos)
return tiles, image # now I now the list of positions to fill the bg and the exact file to use
def draw(window, background, bg_image, player):
for tile in background:
window.blit(bg_image, tile) # drawing the image at every position
player.draw(window)
pygame.display.update()
def handle_move(player): #check keys and collision
keys = pygame.key.get_pressed()
player.x_vel = 0; #as moveleft change the velocity we have to change it to zero so w
if keys[pygame.K_LEFT]:
player.move_left(PLAYER_VEL)
if keys[pygame.K_RIGHT]:
player.move_right(PLAYER_VEL)
def main(window):
clock = pygame.time.Clock()
background, bg_image = get_background("Blue.png")
player = Player(100,100,50,50)
run = True
while(run):
clock.tick(FPS) #fix the refresh rate to this otherwise it will be dpending on the computer power
for event in pygame.event.get():
if event.type == pygame.QUIT: #if teh program detect an event of the user Quiting the game
run = False
break
player.loop(FPS)
handle_move(player)
draw(window, background, bg_image, player)
pygame.quit()
quit() #quiting the python itself
if __name__ == "__main__":
main(window) #when we run the file go to the main function with this arg
comparing the code with the original author code, change my code
I think that the problem lies in the following line of the Player.draw function:
self.sprite = self.SPRITES["idle_"+self.direction][0]
The sprite attribute was already set in the update_sprite function to the correct sprite taking the animation count and state into an account. However, this line resets it to the first image ([0]) from the idle spritesheet ("idle_"), without taking the animation count or state into an account. This does make that only this sprite and not the correct one is being drawn.
Removing the line should resolve the problem.
I'm trying to create a platformer game, and I want to maintain 60 FPS in it without having the sprite animations move really quickly. I've seen other answers on how to do so using the time module, but I don't really understand how to apply that.
Main Code:
import pygame
import os
import sys
import random
from pygame.locals import *
import spritesheet
import time
pygame.init()
clock = pygame.time.Clock()
FPS = 60
prev_time = time.time()
pygame.display.set_caption('Platformer')
BG_COLOR = (50, 50, 50)
BLACK = (0, 0, 0)
WIN_SIZE = (1920,1080)
WIN = pygame.display.set_mode(WIN_SIZE, 0, 32)
# CONFIGURING Animations
sprite_sheet_img_IDLE = pygame.image.load('Spritesheets/Outline/120x80_PNGSheets/_Idle.png')
sprite_sheet = spritesheet.SpriteSheet(sprite_sheet_img_IDLE)
IDLE_FRAMES = []
IDLE_STEPS = 9
IDLE_INDEX = 0
INDEX = 0
IDLE_ANIM_SPEED = 20
for x in range(IDLE_STEPS):
newFRAME = sprite_sheet.get_image(x, 120, 80, 3.5, BLACK)
IDLE_FRAMES.append(newFRAME)
while True:
clock.tick(FPS)
now = time.time()
dt = now - prev_time
prev_time = now
WIN.fill(BG_COLOR)
WIN.blit(IDLE_FRAMES[], (0, 0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
if IDLE_INDEX < 8:
IDLE_INDEX += 1
else:
IDLE_INDEX = 0
INDEX += 1
Spritesheet Class:
import pygame
class SpriteSheet():
def __init__(self, image):
self.sheet = image
def get_image(self, frame, width, height, scale, colour):
image = pygame.Surface((width, height)).convert_alpha()
image.blit(self.sheet, (0, 0), ((frame * width), 0, width, height))
image = pygame.transform.scale(image, (width * scale, height * scale))
image.set_colorkey(colour)
return image
Here is the spritesheet if anyone wants to replicate my situation:
I'm 100% confident there is a better way to do it, but I would create a new variable called animation_tick and add one to it each iteration in the while loop. Only call the sprite change every n ticks to add delay between changes.
Example code with only necessary parts:
while True:
if animation_tick == 20: #change 20 to how ever many ticks in between animation frames
if IDLE_INDEX < 8:
IDLE_INDEX += 1
else:
IDLE_INDEX = 0
animation_tick = 0 #reset tick back to 0 after changing frame
animation_tick += 1 #add 1 each iteration of the while loop
It's practically just a for loop inside a while loop
Below is the basic code for creating and managing the pipes of the game:
import pygame as pg
import sys,os,math,time,random
# colours
white = (255,255,255)
red = (255,0,0)
green = (0,255,0)
# general stuff
WIDTH = 1024
HEIGHT = 576
FPS = 60
# other
all_events = [pg.QUIT, pg.ACTIVEEVENT, pg.KEYDOWN, pg.KEYUP, pg.MOUSEMOTION,
pg.MOUSEBUTTONUP, pg.MOUSEBUTTONDOWN, pg.VIDEORESIZE,
pg.VIDEOEXPOSE, pg.USEREVENT]
pg.init()
screen = pg.display.set_mode((WIDTH, HEIGHT))
clock = pg.time.Clock()
# Class to manage Pipes
class Pipe_Manager:
def __init__(self):
self.pipe_width = 50
self.pipes = []
self.pipe_speed = 5
self.max_tick = 75
self.spawn_tick = self.max_tick
def manage_pipes(self):
self.spawner()
self.manage()
self.display()
def make_pipe(self):
height = random.randint(100,326)
gap = random.randint(100,250)
surf1 = pg.Surface((self.pipe_width, height))
surf1.fill(green)
surf2 = pg.Surface((self.pipe_width, HEIGHT - (height + gap)))
surf2.fill(green)
# surface, (x,y) and vertical height
pipe = [surf1, [WIDTH, 0], height]
pipe2 = [surf2, [WIDTH, height + gap], HEIGHT - (height + gap)]
self.pipes.append(pipe)
self.pipes.append(pipe2)
def spawner(self):
if self.spawn_tick == self.max_tick:
self.make_pipe()
self.spawn_tick = 0
self.spawn_tick += 1
def manage(self):
for pipe in self.pipes:
# move the pipe
pipe[1][0] -= self.pipe_speed
# check if it's off screen
if pipe[1][0] + self.pipe_width < 0:
self.pipes.remove(pipe)
def display(self):
for pipe in self.pipes:
screen.blit(pipe[0], (pipe[1][0], pipe[1][1]))
################################################################################
pg.event.set_blocked(all_events)
pg.event.set_allowed([pg.QUIT, pg.KEYDOWN])
pipe_manager = Pipe_Manager()
loop = True
while loop:
screen.fill(white)
pipe_manager.manage_pipes()
pg.display.update()
clock.tick(FPS)
The pipes seem to shake as they move horizontally and sometimes the top pipe becomes misaligned from the bottom one.
I hope this isn't a problem specific to my computer because I have abstracted away a significant amount of my flappy-bird-clone code and the source of this pipe lag problem must lie somewhere in here.
The problem is this piece of code:
for pipe in self.pipes:
# move the pipe
pipe[1][0] -= self.pipe_speed
# check if it's off screen
if pipe[1][0] + self.pipe_width < 0:
self.pipes.remove(pipe)
Here you change the list you're currently iterating over. Once a pipe gets removed from the list, the next one misses one movement step.
Take a look at the following example in which you can spot the problem yourself (see how 5 is missing in the output because we removed 4):
>>> l = [1,2,3,4,5,6,7,8,9,10]
>>> for x in l:
... if x == 4:
... l.remove(x)
... print(x)
...
1
2
3
4
6
7
8
9
10
>>>
(There's a reason other languages forbid changing the sequence you're currently iterating).
A simple fix is to make a copy of the list first:
for pipe in self.pipes[:]:
For smoother movement, try increasing your framerate and use timestepping.
Here's a possible way:
import pygame as pg
import sys,os,math,time,random
# colours
white = (255,255,255)
red = (255,0,0)
green = (0,255,0)
# general stuff
WIDTH = 1024
HEIGHT = 576
FPS = 120
# other
all_events = [pg.QUIT, pg.ACTIVEEVENT, pg.KEYDOWN, pg.KEYUP, pg.MOUSEMOTION,
pg.MOUSEBUTTONUP, pg.MOUSEBUTTONDOWN, pg.VIDEORESIZE,
pg.VIDEOEXPOSE, pg.USEREVENT]
pg.init()
screen = pg.display.set_mode((WIDTH, HEIGHT))
clock = pg.time.Clock()
class Pipe:
def __init__(self, img, pos):
self.img = img
self.pos = pos
# Class to manage Pipes
class Pipe_Manager:
def __init__(self):
self.pipe_width = 50
self.pipes = []
self.pipe_speed = 0.3
self.max_tick = 1500
self.spawn_tick = self.max_tick
def manage_pipes(self, dt):
self.spawner(dt)
self.manage(dt)
self.display()
def make_pipe(self):
height = random.randint(100,326)
gap = random.randint(100,250)
surf1 = pg.Surface((self.pipe_width, height))
surf1.fill(green)
surf2 = pg.Surface((self.pipe_width, HEIGHT - (height + gap)))
surf2.fill(green)
pipe = Pipe(surf1, pg.Vector2(WIDTH, 0))
pipe2 = Pipe(surf2, pg.Vector2(WIDTH, height + gap))
self.pipes.append(pipe)
self.pipes.append(pipe2)
def spawner(self, dt):
if self.spawn_tick >= self.max_tick:
self.make_pipe()
self.spawn_tick = 0
self.spawn_tick += dt
def manage(self, dt):
for pipe in self.pipes[:]:
# move the pipe
pipe.pos.x -= self.pipe_speed * dt
# check if it's off screen
if pipe.pos.x + self.pipe_width < 0:
self.pipes.remove(pipe)
def display(self):
for pipe in self.pipes:
screen.blit(pipe.img, pipe.pos)
################################################################################
pg.event.set_blocked(all_events)
pg.event.set_allowed([pg.QUIT, pg.KEYDOWN])
pipe_manager = Pipe_Manager()
loop = True
dt=0
while loop:
for e in pg.event.get():
if e.type == pg.QUIT:
loop = False
screen.fill(white)
pipe_manager.manage_pipes(dt)
pg.display.update()
dt=clock.tick(FPS)
This question already has answers here:
How can I force division to be floating point? Division keeps rounding down to 0?
(11 answers)
Closed 5 years ago.
I am trying to make a healthbar in pygame and it is supposed to show in percent. The formula that I'm using is (lives/mlives)*100, mlives stands for max lives.
When the game begins, player has 3 lives to start with. Each time the shielding gets down to 0 or less, -1 is removed from lives. So on the first run everything works perfectly fine. But when -1 if removed from lives the health bar shows 0 and i have tried to print the formula on terminal without multiplying it with 100 to see what it would show when lives / mlives.
now when player has 3 lives the formula gives 1.0 and that is 100%
when the player has 2 lives the formula gives 0.0 instead of 0.66 which is 66%
player has 3 lives
player has 2 lives
game.py -- funciton name is draw_health_bar
#!/usr/bin/python
import os, sys, player, enemy, config, random
from os import path
try:
import pygame
from pygame.locals import *
except ImportError, err:
print 'Could not load module %s' % (err)
sys.exit(2)
# main variables
FPS, BGIMG = 30, 'FlappyTrollbg.jpg'
# initialize game
pygame.init()
screen = pygame.display.set_mode((config.WIDTH,config.HEIGHT))
pygame.display.set_caption("FlappyTroll - Python2.7")
# background
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((255,255,255))
img_dir = path.join(path.dirname(__file__), 'img')
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self)
self.width = config.WIDTH
self.height = config.HEIGHT
self.image = pygame.image.load(path.join(img_dir,image_file)).convert_alpha()
self.image = pygame.transform.scale(self.image,(self.width,self.height))
self.rect = self.image.get_rect()
#self.rect.left, self.rect.top = location
self.rect.x, self.rect.y = location
self.speedx = 5
def update(self):
self.rect.x -= self.speedx
if self.rect.x <= -config.WIDTH:
self.rect.x = config.WIDTH
def draw_shield_bar(surf, x, y, percent):
if percent <= 0:
percent = 0
bar_length = 100
bar_height = 10
fill = (percent / 100) * bar_length
bar_outline = pygame.Rect(x,y,bar_length,bar_height)
bar_filled = pygame.Rect(x,y, fill, bar_height)
pygame.draw.rect(surf, config.green, bar_filled)
pygame.draw.rect(surf, config.white, bar_outline, 2)
def draw_health_bar(surf, x, y, percent):
bar_length = 100
bar_height = 10
fill = float(percent) * bar_length
bar_outline = pygame.Rect(x,y,bar_length,bar_height)
bar_filled = pygame.Rect(x,y, fill, bar_height)
pygame.draw.rect(surf, config.red, bar_filled)
pygame.draw.rect(surf, config.white, bar_outline, 2)
def draw_text(surf, text, size, x, y):
font_ = pygame.font.SysFont("Arial", size)
show_kills = font_.render(text, True, config.white)
surf.blit(show_kills, (x, y))
# blitting
screen.blit(background,(0,0))
pygame.display.flip()
# clock for FPS settings
clock = pygame.time.Clock()
def newSprite(group, obj):
group.add(obj)
def main():
all_sprites = pygame.sprite.Group()
bgs = pygame.sprite.Group()
creature = pygame.sprite.Group()
attack = pygame.sprite.Group()
eattack = pygame.sprite.Group()
bgs.add(Background(BGIMG, [0,0]))
bgs.add(Background(BGIMG, [config.WIDTH,0]))
troll = player.FlappyTroll()
creature.add(troll)
for i in range(0,4):
newSprite(all_sprites, enemy.TrollEnemy())
# variable for main loop
running = True
# init umbrella
# event loop
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for e in all_sprites:
if (pygame.time.get_ticks() - e.starttime) >= e.delay:
newEAtk = enemy.EnemyAttack(e.rect.x, e.rect.y)
eattack.add(newEAtk)
e.starttime = pygame.time.get_ticks()
keys = pygame.key.get_pressed()
if (keys[pygame.K_SPACE]) and (pygame.time.get_ticks() - troll.starttime) >= troll.delay:
newAtk = player.FlappyAttack(troll.rect.x, troll.rect.y)
attack.add(newAtk)
troll.starttime = pygame.time.get_ticks()
b_gets_hit = pygame.sprite.groupcollide(eattack, attack, True, True)
p_gets_hit_eatk = pygame.sprite.groupcollide(eattack, creature, True, False)
gets_hit = pygame.sprite.groupcollide(all_sprites, attack, True, True)
p_gets_hit = pygame.sprite.groupcollide(all_sprites, creature, True, False)
if gets_hit or p_gets_hit:
newEnemy = enemy.TrollEnemy()
newSprite(all_sprites, newEnemy)
for p in creature:
if p_gets_hit or p_gets_hit_eatk:
troll.shield -= random.randint(1,5)*1.5
if troll.shield <= 0:
troll.lives -= 1
troll.shield = 100
if troll.lives == 0:
print "#--- GAME OVER ---#"
break
screen.fill([255, 255, 255])
# update
bgs.update()
all_sprites.update()
creature.update()
attack.update()
eattack.update()
# draw
bgs.draw(screen)
all_sprites.draw(screen)
creature.draw(screen)
attack.draw(screen)
eattack.draw(screen)
draw_shield_bar(screen, 5, 5, troll.shield)
draw_health_bar(screen, 5, 20, (troll.lives/troll.mlives))
draw_text(screen, ("Lives: "+str(troll.lives)), 20, (config.WIDTH / 2), 0)
print float(troll.lives/troll.mlives)
# flip the table
pygame.display.flip()
pygame.quit()
if __name__ == '__main__':
main()
player.py (object name is Troll in game.py)
import pygame, config
from pygame.locals import *
from os import path
from random import randint
img_dir = path.join(path.dirname(__file__), 'img')
class FlappyTroll(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.width = 64
self.height = 64
self.image = pygame.image.load(path.join(img_dir,"flappytroll.png")).convert_alpha()
self.image = pygame.transform.scale(self.image,(self.width,self.height))
self.rect = self.image.get_rect()
self.rect.x = self.width*2
self.rect.y = config.HEIGHT/2-self.height
self.speedy = 5
self.starttime = pygame.time.get_ticks()
self.delay = 500
self.shield = 100
self.lives = 3
self.mlives = 3
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
self.rect.y -= self.speedy*2
elif self.rect.y < config.HEIGHT-self.height*1.5:
self.rect.y += self.speedy
Try dividing by 3.0 rather than just 3. I'm pretty sure this will work as it is what Python 2's integer division is coded as. Hope this helps :-) P.S. If you do any more additions, subtractions, divisions or multiplications, be sure to put .0 at the end unless you want another D.P. e.g. 2 * 7.0.
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()