I'm trying to make an instance class (Block) spawn when the mouse is clicked and fade out slowly, and i want to be able to spawn an infinite number of blocks that fade out provided that the mouse is clicked fast enough.
To do this, I want to initiate the fade() function when the instance is first spawned and set ticks to 255 and set the alpha to ticks.
However, when using a while loop, the function completes itself without updating the display because the program gets constrained to the while loop in the fade() function.
Can someone help me with calling the fade function 255 times per instance?
import pygame,sys,time,Blockm
from pygame.locals import *
black,white=(0,0,0),(255,255,255)
size=w,h=1400,800
screen=pygame.display.set_mode(size)
pygame.init()
class Block(object):
sprite = None
def __init__(self, x, y):
if not Block.sprite:
Block.sprite = pygame.image.load("Block.png").convert_alpha()
self.rect = Block.sprite.get_rect(top=y, left=x)
self.fade()
def fade(self):
ticks=255
Block.sprite.set_alpha(ticks)
while ticks!=0:
ticks-=1
print(ticks)
while True:
blocks = []
mmraw=pygame.mouse.get_pos()
mmx,mmy=mmraw[0]-(pygame.image.load("Block.png").get_width())/2,mmraw[1]-(pygame.image.load("Block.png").get_width())/2
for event in pygame.event.get():
if event.type== pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
print('click')
blocks.append(Block(mmx,mmy))
for block in blocks:
screen.blit(block.sprite, block.rect)
print('trying to blit')
print(Block.sprite.get_offset())
pygame.display.update()
I have one solution for you that ought to accomplish what you want it to, but I concede that it does not answer the question exactly as asked.
The first and most hindering problems you may be having are that you need to use pygame.image.load("Block.png").convert() and not pygame.image.load("Block").convert_alpha(), since .convert_alpha() doesn't work with .set_alpha(..).
It's also possible that you aren't noticing when blocks fade in any solution because screen isn't being refreshed prior to update. New, faded blocks are being drawn over 'brighter' ones, producing no difference (overlapping blocks notwithstanding). I've added a stopgap to my solution code below that fills the screen with blue, but I imagine you'll want something different. It's marked with a comment.
What I would suggest is having each Block process its alpha locally during a call from the main loop in your for block in blocks: block before you blit it. This version of your code should give you the result you want, although it does it using just the main loop, rather than parallel loops like you were asking about...
import pygame,sys,time,Blockm
from pygame.locals import *
black,white=(0,0,0),(255,255,255)
size=w,h=1400,800
screen=pygame.display.set_mode(size)
pygame.init()
class Block(object):
sprite = None
def __init__(self, x, y):
if not Block.sprite:
Block.sprite = pygame.image.load("Block.png").convert()
# ^ MODIFIED: .convert_alpha() won't recognize the effects of .set_alpha(), so use regular .convert() instead.
self.rect = Block.sprite.get_rect(top=y, left=x)
self.ticks = 255 # NEW: Set a local tick count.
# REMOVED `self.fade()`: This runs on main now.
def fade(self):
## REPURPOSED METHOD: Runs each frame that the Block is active. Called on main loop.
# MODIFIED BLOCK: Update local tick count before setting the class's sprite alpha.
self.ticks -= 1
Block.sprite.set_alpha(self.ticks) # MOVED, MODIFIED: uses local tick count for alpha.
print(self.ticks) # UPDATED: Using local ticks.
blocks = [] # MOVED: Make a list for the Blocks, but don't clear it on frame.
while True:
mmraw=pygame.mouse.get_pos()
mmx,mmy=mmraw[0]-(pygame.image.load("Block.png").get_width())/2,mmraw[1]-(pygame.image.load("Block.png").get_width())/2
# ^ There may be a tidier way of doing this. Described in solution body...
for event in pygame.event.get():
if event.type== pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
print('click')
blocks.append(Block(mmx,mmy))
screen.fill((22,22,222))
# ^ NEW: Fill the screen with some backdrop so that the erasure is obvious!
# If you don't do this, you'll be blitting faded images over brighter ones and there will be no real change!!
for block in blocks:
block.fade() # NEW: Update this block.
if block.ticks < 1: # NEW BLOCK: If the block has become invisible...
blocks.remove(block) # NEW: Expunge this invisible block.
continue # NEW: Immediately move on to the next block.
screen.blit(Block.sprite, block.rect)
print('trying to blit')
print(Block.sprite.get_offset())
pygame.display.update()
There's a small problem with it, which is that vanishing Blocks trigger a 'flicker' in other remaining Blocks (or at least the next one in blocks) and I'm not sure why or how.
While I was looking, I found a few other things you might want to consider:
...in class Block::
-Consider using sprite = pygame.image.load("Block.png").convert() instead of sprite = None. This way, you can use something like mmx,mmy = mmraw[0] - Block.sprite.get_rect.centerx, mmraw[1] - Block.sprite.get_rect().centery instead of loading the image for a moment, just to know its size. Since all Blocks use the same graphic, it shouldn't make a difference, and this way, you won't have to reacquire the offset if you change Block.sprite during runtime!
-Consider assigning a copy of Block's sprite to each instance instead of using the class's Surface. This will take up more processing power, but only momentarily if you use it as a return instead. For instance:
class Block(object):
...
def fade(self):
sprite = Block.sprite.copy()
sprite.set_alpha(self.ticks)
self.ticks -= 1
return sprite
...
while True: # main loop
...
for block in blocks:
screen.blit(block.fade(), block.rect) # Although there are more Pythonic ways of writing this.
Alternatively, you could use screen.blit(sprite, self.rect) in Block.fade() rather than on main and forego the return entirely. Because the alpha is set locally, it won't have to be reset every time fade runs, and Block's unbound sprite can stay fresh!
Anyway, I hope this solves your problem, even if it does not (exactly) answer your question!
You probably want to use threads. These allow you to run multiple things at once. For your code, change it like this:
from threading import Thread
import pygame, sys, time, Blockm
from pygame.locals import *
black = (0,) * 3
white = (255,) * 3
size = w, h = 1400, 800
screen = pygame.display.set_mode(size)
pygame.init()
class Block(object):
sprite = None
def __init__(self, x, y):
if not Block.sprite:
Block.sprite = pygame.image.load("Block.png").convert_alpha()
self.rect = Block.sprite.get_rect(top=y, left=x)
Thread(target=self.fade) # Made this a Thread so it runs separately.
def fade(self):
for tick in range(255, 0, -1):
Block.sprite.set_alpha(tick)
print(tick)
def game():
while True:
# The contents of that while True loop at the end
def main():
Thread(target=Main)
if __name__ == "__main__":
main()
This also adds an entry point to your program, which was lacking. Also, Block.fade wouldn't actually do what you want, as you only set_alpha once. I fixed that with a for loop instead. Also, note that you can now just return to break out of the whole game.
Related
I have made some little games with the pygame module and it had been fun. However, right now i´m trying to move the character with a piano via midi using pygame.midi. there´s the problem. I did it but not in the way i wanted to because
the character moves sometimes and other it doesn´t. I think i know where is the problem: If i understand correctly, the pygame.event.get() function "catch" the events always, it does not matter when (inside the main loop of the game), so i think that with my code this is not happening, i mean, i believe that the player not always do what is supposed to do because the midi input has to be in a specific "time" not as pygame.event.get(). I looked into the pygame documentation and there seems to be a function that does the trick: pygame.midi.midis2events(), but i don´t know how to use it.The question is just:
How can i move the character through the piano via midi with pygame in order that the character moves everytime?
import pygame as pg, pygame.midi
WIDTH = 800
HEIGHT = 600
FPS = 60
BLACK = (0,0,0)
BLUE = (0,0,255)
pg.init()
pg.midi.init()
screen = pg.display.set_mode((WIDTH,HEIGHT))
pg.display.set_caption('STACKOVERFLOW_EXAMPLE_MIDI')
clock = pg.time.Clock()
running = True
inp = pg.midi.Input(1)
x = WIDTH//2
y = HEIGHT//2
speedx = 0
midi_list = []
while running:
clock.tick(FPS)
if inp.poll():
midi_value = inp.read(1000)[0][0][1]
if midi_value==57:
midi_list.append(midi_value)
if len(midi_list)==1:
speedx = -1
else:
speedx = 0
midi_list = []
if midi_value == 63:
running = False
x = x+speedx
screen.fill(BLUE)
pg.draw.rect(screen,BLACK,[x,y,50,60])
pg.display.update()
pg.display.flip()
pg.quit()
I need that the player moves to the left (or to the right) when the piano´s key A3 (a specific note) is being pressed and that the player stops moving when
the key has been released. In this particular fragment of the code, but also to do things like press a piano´s key an shoot one bullet regardless of whether is being pressed or not, in short, to do the same things i can do with the keyboard events.
ok! i found and answer that works well for the time being:
if inp.poll():
s(0.1)
midi_values = inp.read(1000)
for midi in midi_values:
midi_value = midi[0][1]
#Movement to the left
if midi_value==57:
midi_list.append(midi_value)
if len(midi_list)==1:
speedx = -50
Modifying the code in order to iterate the list midi_values gives me the correct input-output response without the inestability which i experienced. Also i did introduce a sleep(0.1) delay and works well without to much cpu use. I found the answer here:
https://www.reddit.com/r/learnpython/comments/36dxu9/python_whileloop_cpu_demanding/
The only problem which i find now is that the pygame window stops with the message no responde (doesn´t respond or something similar) and it stops working.
For example the cube which i´m moving with the keys of the piano doesn´t do anything but the key to close the program does work fine!!
I'm looking to display a message on the screen when a player wins, and get the text to display for 5 seconds, and then go back to the main menu start screen. Using the time.delay function however, my screen pauses and then displays the text in a flash, but then immediately goes to the startscreen. Is there a more efficient way of getting the text to be displayed for long enough to be read?
Below is the function I use to actually display the message:
def winnerPlayerOne():
screen.fill(PINK)
winnerP1Message = winnerFont.render("Congrats Player 1. You Win!", True, WHITE)
screen.blit(winnerP1Message, ((400 - (winnerP1Message.get_width()/2)),(300 - (winnerP1Message.get_height()/2))))
pygame.display.update()
pygame.time.delay(5000)
startscreen()
And below this is how I call this function, within the main loop:
if playeroneScore == 5:
winnerPlayerOne()
if playertwoScore == 5:
winnerPlayerTwo()
Any help would be greatly appreciated!
Benjamin's answer probably will work fine in your case. But if you want something that doesn't interfere with your game's visuals, I would consider setting a timer like so...
WITHIN GAME LOOP:
if gameWonScreen:
screen.fill(PINK)
winnerP1Message = winnerFont.render("Congrats Player 1. You Win!", True, WHITE)
screen.blit(winnerP1Message, ((400 - (winnerP1Message.get_width()/2)),(300 - (winnerP1Message.get_height()/2))))
timer = timer + elapsed/1000
elapsed = fpsClock.tick(FPS)
if timer > timeWinScreen:
gameWonScreen = false
Initialize 'timeWinScreen' to the desired message duration at the start of the application and set 'timer' to '0' and gameWonScreen to 'true' when the player wins. Using elapsed = fpsClock.tick(FPS) will hold the time value since the last tick. You don't need to use it for this process (you could just use a fraction of your FPS) but using 'elapsed' is good practice because it helps with smoothing animations of certain objects.
Try out pygame.time.wait(5000). It should behave more in the way you are expecting. It does prevent any code running in the background as well, but that didn't seem like it would be an issue for your use case.
Try this:
Remember that in the class below, time is not the way you measure time in pygame. It is the number of loop happened in the main loop.
class disp_txt:
def __init__(self,text,time,destination):
self.text = text
self.time = time
self.destination = destination
self.shfnt = pygame.font.SysFont("comicsens",30,False)
self.shtxt = self.shfnt.render(self.text,0,(255,0,0))
def show(self,surface):
if self.time>0:
surface.blit(self.shtxt,self.destination)
self.time -= 1
hint = disp_txt("text",100,(100,400)) #example
hint.show(screen) #in the main loop or where you are drawing
I am struggling with a problem I can't solve.
I want to change the image of my character when the enemy hurts it.
In pseudocode, it would be like this:
*If enemy collides -> player close eyes and get red;*
*After 0.50 seg -> player gets normal back again*
I tried to do it with Clock and Timers but it is very difficult. I only get changing the image, but not getting it back.
Any ideas?
Thank you!
I would assume it's as easy as this. pygame.time.set_timer(x, y) basically creates an x event on the event stack every y milliseconds.
# Main game loop
while True:
# display stuff and other parts of your game
# replace this with whatever detection you have for collision
if enemycollide:
player.setSprite(1)
pygame.time.set_timer(14, 500) # 500 milliseconds = .5 seconds
# event handling
for event in pygame.event.get():
if event.type == 14:
player.setSprite(0)
pygame.time.set_timer(14, 0) # Disable the timer
I created this function that randomly spawn a dot, but because its in my game loop the dot just does not stay where it is. I want it to spawn a dot and then wait 6 seconds and spawn another one randomly, but the first still being there. Because there is other things happening on the screen at the same time I cant use time.sleep(6). Any help solving this would be really appreciated.
def random_spawn():
image_x = random.randrange(122, 476)
image_y = random.randrange(90, 350)
screen.blit(Green, (image_x, image_y))
Don't use time.sleep. In general, in game programming, you never want to use time.sleep for any reason.
In your case, you just need to check a timer or clock every so often, and if the time is up, run your spawn function.
This is what a typical game loop will look like:
while True:
# get/handle player input
# update game state
# update sprites
# draw background
# draw sprites
# flip display
In your case, when you update your game state, you should check how much time has passed since you last spawned your random sprite. If it has been longer than 6 seconds, spawn a new one.
It will look something like this:
if time.clock() - last_spawn_time > 6.0:
random_spawn()
I think a way to fix this would be to only randomly spawn a dot if certain conditions are met. So you can define a function that randomly generates new coordinates outside of your main function (and returns them). Then your main game loop will take care of rendering the dot on the screen if certain conditions are met. For example:
image_x, image_y = get_random_coordinates()
while True:
if time.clock() - last_spawn_time > 6.0:
image_x, image_y = get_random_coordinates()
last_spawn_time = time.clock()
screen.blit(Green, (image_x, image_y))
So the idea is that you draw the same random coordinates, and keep drawing those same coordinates until the time between your last spawning and now is more than 6 seconds.
If you are trying to avoid using threads, and just using a while loop, perhaps this might work:
import time
def my_function():
print 'it has been about 6 seconds'
prev_time_stamp = time.time()
while True:
#do something
#do something else
if time.time() > prev_time_stamp + 6:
prev_time_stamp = time.time()
my_function()
#do something else
import random module:
import random
create random coordinates:
rand_X = (random.randrange(0, display_width - image_width)
rand_Y = (random.randrange(0, display_height - image_height)
blit image:
gameDisplay.blit(yourimage,(randAppleX,randAppleY))
I have written a pygame script that renders a horizontally moving text across the screen. It works, but when I change the string to a non-latin string (Persian), it does not work completely. I mean it does everything (animation, rendering ...) but the characters are depareted from each, though they are shown healthy, but are not in the expected order. I mean each letter is shown well, but a word is not rendered, a sequence of characters are rendered.
the string should beسلام
but it is rendered م ا ل س
Here is the full code:
import sys, pygame; #importing the required modules
#initialize the game module
pygame.init();
#setting the display properties and put in a variable
width = 800; #px
height = 600; #px
display_info = (width, height);
screen = pygame.display.set_mode(display_info);
# adding contents
arial = pygame.font.SysFont("Tahoma", 21);
text = arial.render("سلام", 1, (233,114,93), (255,255,255));
clock = pygame.time.Clock();
x = 0;
#starting the game loop
while True :
#it sets the timing of the loop-execution (40 fps)
clock.tick(40);
#getting the list of event on each game loop
for event in pygame.event.get() :
#if the event is QUIT (if the user has acted to close the application)
if event.type == pygame.QUIT :
pygame.quit(); #terminate python-side
sys.exit(); #exits system-side
#refreshing the screen with overall blackness
screen.fill((0,0,0));
#rendering the text. We do not move the text, we just increment the position on each loop
screen.blit(text, (x, height/3));
#incrementing the x-position
x += 1;
#updating the display to the latest changes
pygame.display.update();
#end of the while loop
This is a known bug in pygame and is currently classified as "won't fix", because:
To support all languages, we'd need to include a much larger font file, and changing files would break existing games.
One user in the thread suggests that python-fribidi can be used to get the correct behaviour.