This question already has answers here:
How to load and play a video in pygame
(3 answers)
Closed 1 year ago.
I am looking to add videos in my game (such as cut scenes or an animated menu screen).
I looked around for a bit and it seems pygame doesn't support video playing anymore, so I was wondering if there was another way to get videos playing while being well integrated in my game, such as having the video playing in the background and having pygame elements (start button etc.) in the foreground.
There's an example somewhere (I was unable to find an original link) of using FFMPEG and another python module to decode the frames via a pipe and read these through into PyGame for displaying. I copied a code snippet (I thought from SO), and forgot about it.
I have now adapted that technique to make a VideoSprite. It uses FFMPEG to decode (and rescale) the video stream, where it's read during the sprites update() to get the next frame.
This is a very rough implementation, but I hope it gives you an idea of what's possible. While it would be nice if PyGame would just play the videos on its own, at least this method hands the video decoding and rescaling off to a subprocess where it hopefully runs on another CPU.
(EDIT: added handler for video ending, and proper FPS control)
import pygame
import subprocess
# Window size
WINDOW_WIDTH = 600
WINDOW_HEIGHT = 400
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
DARK_BLUE = ( 3, 5, 54)
### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("Video Sprite")
class VideoSprite( pygame.sprite.Sprite ):
FFMPEG_BIN = "/usr/bin/ffmpeg" # Full path to ffmpeg executable
def __init__(self, rect, filename, FPS=25 ):
pygame.sprite.Sprite.__init__(self)
command = [ self.FFMPEG_BIN,
'-loglevel', 'quiet',
'-i', filename,
'-f', 'image2pipe',
'-s', '%dx%d' % (rect.width, rect.height),
'-pix_fmt', 'rgb24',
'-vcodec', 'rawvideo', '-' ]
self.bytes_per_frame = rect.width * rect.height * 3
self.proc = subprocess.Popen( command, stdout=subprocess.PIPE, bufsize=self.bytes_per_frame*3 )
self.image = pygame.Surface( ( rect.width, rect.height ), pygame.HWSURFACE )
self.rect = self.image.get_rect()
self.rect.x = rect.x
self.rect.y = rect.y
# Used to maintain frame-rate
self.last_at = 0 # time frame starts to show
self.frame_delay = 1000 / FPS # milliseconds duration to show frame
self.video_stop = False
def update( self ):
if ( not self.video_stop ):
time_now = pygame.time.get_ticks()
if ( time_now > self.last_at + self.frame_delay ): # has the frame shown for long enough
self.last_at = time_now
try:
raw_image = self.proc.stdout.read( self.bytes_per_frame )
self.image = pygame.image.frombuffer(raw_image, (self.rect.width, self.rect.height), 'RGB')
#self.proc.stdout.flush() - doesn't seem to be necessary
except:
# error getting data, end of file? Black Screen it
self.image = pygame.Surface( ( self.rect.width, self.rect.height ), pygame.HWSURFACE )
self.image.fill( ( 0,0,0 ) )
self.video_stop = True
### Create Video Area
video_sprite1 = VideoSprite( pygame.Rect( 100, 100, 320, 240 ), '1975_test_pattern.mp4' )
video_sprite2 = VideoSprite( pygame.Rect( 100, 100, 160, 90 ), '/home/kingsley/Videos/rocket.avi' ) # 640x360
#sprite_group = pygame.sprite.GroupSingle()
sprite_group = pygame.sprite.Group()
sprite_group.add( video_sprite1 )
sprite_group.add( video_sprite2 )
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.MOUSEBUTTONUP ):
# On mouse-click
pass
# Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
video_sprite2.rect.y -= 10
if ( keys[pygame.K_DOWN] ):
video_sprite2.rect.y += 10
if ( keys[pygame.K_LEFT] ):
video_sprite2.rect.x -= 10
if ( keys[pygame.K_RIGHT] ):
video_sprite2.rect.x += 10
# Update the window, but not more than 60fps
sprite_group.update()
window.fill( DARK_BLUE )
sprite_group.draw( window )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(25) # matching my video file
pygame.quit()
Obviously, since it's just the video stream, there's no sound. But for all intents and purposes it's just another sprite. When the video runs out we catch the error and go black.
NOTE: Sometimes when I run this, it breaks the terminal echoing in Linux. I suspect it's something to do with the subprocess and/or pipe. Running reset fixes this. It seems a common problem with subprocesses.
I wrote a code which seems to work very well with moviepy, I was inspired by that of Kingsley.
import moviepy.editor
import moviepy.video.fx.all
import pygame
class VideoSprite(pygame.sprite.Sprite):
def __init__(self, rect, filename):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((rect.width, rect.height), pygame.HWSURFACE)
self.rect = self.image.get_rect()
self.rect.x = rect.x
self.rect.y = rect.y
self.video = moviepy.editor.VideoFileClip(filename).resize((self.rect.width, self.rect.height))
self.video_stop = False
def update(self, time=pygame.time.get_ticks()):
if not self.video_stop:
try:
raw_image = self.video.get_frame(time / 1000) # /1000 for time in s
self.image = pygame.image.frombuffer(raw_image, (self.rect.width, self.rect.height), 'RGB')
except:
self.video_stop = True
The advantage here is that the video is at the right speed
Related
I am trying to print a million rectangles in 2-3 seconds, however, I can not understand how to use parallelization with GUI.
class Rectangle:
def __init__(self, pos, color, size):
self.pos = pos
self.color = color
self.size = size
def draw(self):
pygame.draw.rect(screen, self.color, Rect(self.pos, self.size))
I want to use something like open MP here to make this process faster
rectangles = []
for i in range(1000000):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
rectangles.append(Rectangle(random_pos, random_color, random_size))
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.lock()
for rectangle in rectangles:
rectangle.draw()
screen.unlock()
pygame.display.update()
Here's an example that generates random rectangles in sub-processes. It's done this way, because it's not possible to draw to the main PyGame surface from anything other than the main thread/process. So if you want to generate items in real-time, do so, but then notify the main-thread somehow to include them. Perhaps by posting a PyGame Event.
The approach taken is to declare a shared subprocess.manager.list for the rectangles, then split the generation. The function rectGenerator() is given a position in the list from which to start, and a count to stop at.
With rectGenerator() as the target, /N/ sub-processes are created. We then loop waiting for the processes to all finish. A nicer approach might be to smarter about this and only paint the blocks that are complete.
Running on my workstation, it takes a while to paint one million rectangles (~10 seconds). To see for yourself, change BLOCK_SIZE to 100000. Probably this could be optimised (it was my first time using multiprocessing for some years), but I'd rather make the code illustrative.
So as I said in my comment, for simple rectangles it's much faster to just do it without sub-processes. But if each sub-process was doing some kind of CPU-bound complex rendering (fractal generating, image filtering, ray-tracing, etc. etc.) then this is a good way to spread the workload amongst your CPUs/cores.
Further reading: The python GIL.
#! /usr/bin/env python3
import pygame
import random
import multiprocessing
CHILDREN = 10 # processes to use
BLOCK_SIZE = 1000 # rectangles per process
RECT_COUNT = BLOCK_SIZE * CHILDREN # ensure an even factor / subprocess
# Window size
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 800
def randomColour():
""" Generate a random "non dark" colour """
MIN = 80 # minimum brightness of colour channel
MAX = 255
return ( random.randint( MIN, MAX ),
random.randint( MIN, MAX ),
random.randint( MIN, MAX ) )
def randomRect( border=5 ):
""" Generate a random PyGame Rect, within the window bounds """
MAX_HEIGHT = WINDOW_HEIGHT - border
MAX_WIDTH = WINDOW_WIDTH - border
# Generate a rect that stays within the screen bounds
x1 = random.randint( border, MAX_WIDTH )
y1 = random.randint( border, MAX_HEIGHT )
x2 = random.randint( border, MAX_WIDTH )
y2 = random.randint( border, MAX_HEIGHT )
if ( x2 < x1 ):
xswap = x2
x2 = x1
x1 = xswap
if ( y2 < y1 ):
yswap = y2
y2 = y1
y1 = yswap
return pygame.Rect( x1, y1, x2-x1, y2-y1 )
class colourRect:
""" Simple class for holding components of a coloured rectangle """
def __init__( self ):
self.rect = randomRect()
self.colour = randomColour()
def draw( self, surface ):
pygame.draw.rect( surface, self.colour, self.rect, 1 )
def __str__( self ):
s = "rect[ %d,%d w=%d, h=%d ], colour[ %d, %d, %d ]" % ( self.rect.x, self.rect.y, self.rect.width, self.rect.height, self.colour[0], self.colour[1], self.colour[2] )
return s
###
### Multiprocessing Target Function
###
def rectGenerator( mp_list, index_from, count ):
""" Populate a section of the mp_list with randomly sized and coloured
rectangles, starting from the index, for the given count """
for i in range( count ):
mp_list[ index_from + i ] = colourRect()
###
### MAIN
###
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE )
pygame.display.set_caption("Plenty o' Rectangles")
# Make a *Huge* list of rectangles
# Note: this runs much faster than using multiprocessing
#all_rects = [ None for i in range( RECT_COUNT ) ] # pre-allocate list
#rectGenerator( all_rects, 0, RECT_COUNT )
print( "### Setting-up Generation" )
manager = multiprocessing.Manager()
all_rects = manager.list( range( RECT_COUNT ) ) # pre-allocate list
subprocesses = [] # keep the process handles so we can watch them
print( "### Starting Generation" )
for i in range( CHILDREN ):
# each subprocess populates a separate region of the rectangle array
p = multiprocessing.Process( target=rectGenerator, args=( all_rects, i*BLOCK_SIZE, BLOCK_SIZE ) )
p.start()
subprocesses.append( p )
print( "### Waiting for Generation" )
# Wait for the generation to complete
found_running = True
while ( found_running ):
found_running = False
for p in subprocesses:
if ( not p.is_alive() ):
p.join()
else:
found_running = True
print( "### Generation Complete" )
print( "--------------------------" )
# Main loop
clock = pygame.time.Clock()
running = True
while running:
time_now = pygame.time.get_ticks()
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
running = False
# Paint the window with rects, a lot of rects takes a while
print( "### Painting starts" )
window.fill( ( 0,0,0 ) )
for r in all_rects:
r.draw( window )
pygame.display.flip()
print( "### Painting ends" )
# Clamp FPS
clock.tick(2) #slow
pygame.quit()
I made my pygame window resizeable by using pygame.RESIZEABLE in pygame.display.set_mode() but now the content (buttons) I put on the screen earlier haven't shifted with the resizing which is expected but I cannot figure out a way to successfully implement this. I don't get any error messages thankfully. The only problem is that I am not getting the output I want. If the start button is at 100, 100 out of a screen of 600,600 I want it to be at 200,200 out of the resized screen which for this example I'm taking 1200,1200 for the sake of simplicity. I tried using percentages of the total screen size but that completely messed up the positioning.
import pygame
#Initializing pygane
pygame.init()
#Setting Screen Size
SCREENSIZE = (1000,700)
SCREEN = pygame.display.set_mode(SCREENSIZE, pygame.RESIZABLE)
class Button():
def __init__(self, x, y, image, scale):
...
def draw(self, surface):
...
#create button instances
start_button = Button(100,100,start_img,0.8)
exit_button = Button(250,250,exit_img,0.8)
setting_button = Button(450,75,setting_img, 0.35)
As suggested, it's pretty easy to do this with percentage size and position.
When the Button is first initialised, there is a given position and size. But there is also a window size and position at startup too. So instead of storing an exact x co-ordinate, instead we also store the relative x co-ordinate as a factor of the window width.
So say the window is 1000 pixels wide. If your button is positioned in the exact middle, at pixel 500 - the relative position is:
initial_x (500) / initial_width (1000) => 0.5
So we're keeping that relative_x of 0.5. Any time we need to re-position the button, the new x will always be relative_x * window_width for any window size. Bigger or smaller, it still works.
So say now the window is stretched to 1500 pixels wide. The new x co-ordinate would be computer as:
relative_x (0.5) * window_width (1500) => 750
Which is obviously still right in the middle. Say the window then shrunk to 300 pixels ~
relative_x (0.5) * window_width (300) => 150
Obviously you need to extend this further for the y, width and height. But that is just more of the exact same thing.
Working example:
Code:
import pygame
WINDOW_WIDTH = 600 # initial size only
WINDOW_HEIGHT= 300
MAX_FPS = 60
WHITE = (255,255,255)
BLACK = ( 0, 0, 0)
RED = (255, 0, 0)
class StretchyButton( pygame.sprite.Sprite ):
""" A Button Sprite that maintains its relative position and size
despite size changes to the underlying window.
(Based on a pyGame Sprite, just to make drawing simpler) """
def __init__( self, window, x, y, image ):
super().__init__()
self.original_image = image.convert_alpha() # re-scaling the same image gets messed-up
self.image = image
self.rect = self.image.get_rect()
self.rect.topleft = ( x, y )
# Calculate the relative size and position, so we can maintain our
# position & proportions when the window size changes
# So "x_percent" is the relative position of the original x co-ord at the original size
window_rect = window.get_rect()
image_rect = image.get_rect()
# The given (x,y) of the button is relative to the initial window size.
# Keep the ratios of the position against window size
self.x_percent = x / window_rect.width
self.y_percent = y / window_rect.height
self.width_percent = image_rect.width / window_rect.width
self.height_percent = image_rect.height / window_rect.height
def reScale( self, window ):
""" When the window size changes, call this to re-scale the button to match """
window_rect = window.get_rect()
# re-calculate the button's position and size to match the new window size
# we calcualted the percentage (relative) positions at startup, so now
# they can just be used to re-position & re-size
self.rect.x = self.x_percent * window_rect.width
self.rect.width = self.width_percent * window_rect.width
self.rect.y = self.y_percent * window_rect.height
self.rect.height = self.height_percent * window_rect.height
# re-scale the button's image too
# always re-scale off an original image, otherwise the image degrades
self.image = pygame.transform.smoothscale( self.original_image, ( self.rect.width, self.rect.height ) )
### I don't like the look of text without some surrounding space
def renderWithBorder( font, text, border=3, foreground=WHITE, background=BLACK ):
""" Render the given text with the given font, but surrounded by a border of pixels """
text = font.render( text, True, foreground, background )
width,height = text.get_rect().size
bigger_surf = pygame.Surface( ( width + border + border, height + border + border ), pygame.SRCALPHA )
bigger_surf.fill( background )
bigger_surf.blit( text, ( border, border ) )
return bigger_surf
###
### MAIN
###
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.RESIZABLE )
pygame.display.set_caption( "Stretchy Buttons" )
font = pygame.font.Font( None, 30 )
# Just a re-square image
red_box = pygame.Surface( ( 64, 64 ), pygame.SRCALPHA )
red_box.fill( RED )
# create a set of buttons
butt0 = StretchyButton( window, 10, 10, red_box )
butt1 = StretchyButton( window, 200, 100, renderWithBorder( font, "The Owl" ) )
butt2 = StretchyButton( window, 200, 150, renderWithBorder( font, "And The Pussycat" ) )
butt3 = StretchyButton( window, 200, 200, renderWithBorder( font, "Went to Sea" ) )
# use a sprite group to hold the buttons, because it's quicker
buttons = pygame.sprite.Group()
buttons.add( [ butt0, butt1, butt2, butt3 ] )
running = True
while running:
clock = pygame.time.Clock() # used to govern the max MAX_FPS
# Handle events
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
running = False
elif ( event.type == pygame.VIDEORESIZE ):
# The window size has changed, re-scale our buttons
#new_width, new_height = event.size
for butt in buttons:
butt.reScale( window )
# paint the window
window.fill( WHITE )
buttons.draw( window )
pygame.display.flip()
clock.tick( MAX_FPS ) # keep the frame-rate sensible, don't waste electricity
so I'm making a game with pygame and I have some decently large tmx files for the map data. I was wondering if there is A, a way to speed it up or B, if I could simultaneously
run both the mainloop and load the map in. That way I could have a loading screen. I tried using multiprocessing but it said it cannot pickle pygame objects. Thanks!
Your code could load the TMX Map in a thread. This will allow the main loop to wait for a "finished loading" event, and do something else while waiting.
The example code below uses the pytmx library. Typically more code is needed to render the map into an image, but I left this out for the sake of making the answer shorter. Maybe it's useful to also post back more status information events during the load. This would allow the main-loop to display a progress indicator or suchlike.
Obviously this example does not do anything with the window, except fill it blue.
import pygame
import threading
import pytmx
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
DARK_BLUE = ( 3, 5, 54 )
CREAM = ( 255, 254, 224 )
MAP_LOAD_DONE = pygame.USEREVENT + 1
MAP_LOAD_ERROR = pygame.USEREVENT + 2
# global to hold map
global_tmx_map = None
# Thread function to load map in the background
class MapLoadThread( threading.Thread ):
def __init__( self, map_name ):
threading.Thread.__init__(self)
self.daemon = True # exit with parent
self.map_name = map_name
def run( self ):
global global_tmx_map
new_event_args = {}
try:
global_tmx_map = pytmx.load_pygame( self.map_name, pixelalpha=True )
# TODO: do more TMX operations in the thread here.
new_event = pygame.event.Event( MAP_LOAD_DONE, new_event_args )
except Exception as e:
new_event_args[ 'exception' ] = e
new_event = pygame.event.Event( MAP_LOAD_ERROR, new_event_args )
# send the load/fail event to the main loop
pygame.event.post( new_event )
# ...
### Window initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption( "TMX Loader Thread Example" )
# Start the Map Loading thread
thread1 = MapLoadThread( "my_map.tmx" )
thread1.start()
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == MAP_LOAD_DONE ):
print( "MAP LOADED OK" );
elif ( event.type == MAP_LOAD_ERROR ):
print( "MAP LOAD FAILED" );
print( "EXCEPTION WAS " + str( event.exception ) )
# Update the window, but not more than 60 FPS
window.fill( DARK_BLUE )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(60)
pygame.quit()
Code is running correctly and as I expected but after 5 seconds the display for graphics freezes forever. It shows no error, nothing, just stops responding.
This is a program to simulate a movement of a large group of objects. They have to move randomly while aimless like Brownian motion. To make this I used Pygame to draw any object as a rectangle of random location, to move them I remove everything and draw them again with their location randomly changed by 1.
I am using pygame to show graphics.
Could you please also suggest a better solution for this problem?
import pygame, random, sys, time, numpy
from pygame.locals import *
black = (0,0,0)
white = (255,255,255)
clock = pygame.time.Clock()
class people():
def __init__(self):
screen = pygame.display.get_surface()
self.x = random.randint(0, 800)
self.y = random.randint(0, 600)
def move(self):
self.x += random.randint(-1, 1)
self.y += random.randint(-1, 1)
if self.x < 0:
self.x = 0
if self.x > 800:
self.x = 800
if self.y < 0:
self.y = 0
if self.y > 600:
self.y = 600
def place(x, y):
screen = pygame.display.get_surface()
pygame.draw.rect(screen, black, [x, y, 10, 10])
def main():
# Initialise screen
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Test')
peoples = []
chosepopul = 1
while chosepopul == 1:
try:
population = abs(int(input("How many people would you like to have")))
chosepopul = 0
except:
print("Number, please")
for i in range(population):
peoples.append(people())
while True:
screen.fill(white)
for obj in peoples:
people.place(obj.x, obj.y)
people.move(obj)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
pygame.quit()
quit()
Everything is working as I expected but freezing inevitably.
UPDATE: If I change input script to constant number, everything is working correctly. So the problem is somehow linked with user interface interactions.
The program stops because the input() blocks the program flow. No further PyGame updates or events are sent & processed. Basically everything comes to a halt, waiting for the user to type.
The best way around this is to write code such that the user does some PyGame on-screen input, rather than in the console. Maybe make a slider or spinner-control to select the number, or plus/minus buttons, whatever.
Alternatively, the program can still use console input in a thread which uses the post() function to send the result to the main PyGame event-loop thread.
I must admit, this answer is of academic interest only, since using the console to input along with a PyGame window is pretty ugly!
Anyway, here is some code. The main python window simply changes colour every 0.5 seconds, while the user can input text in the console with the standard python input() function. The code uses it's own Event enumerated type for posting messages, but these could also just be plain numbers.
This works, as per the OP, because the input() function is called inside a sub-thread of execution. This leaves the main thread free to continually process the PyGame event queue, and paint updates to the window. It's important to only have a single event queue/loop (for reasons beyond the scope of this answer), so the sub-thread "posts" events back to the main thread, rather than acting on the window/events itself.
import threading
import pygame
import enum
# Window size
WINDOW_WIDTH = 200
WINDOW_HEIGHT = 200
DARK = ( 50, 50, 50 )
WHITE = ( 255,255,255 )
RED = ( 255, 55, 55 )
GREEN = ( 5,255, 55 )
BLUE = ( 5, 55,255 )
colour_cycle = [ DARK, WHITE, RED, GREEN, BLUE ]
class UserEvents( enum.IntEnum ):
CLIENT_NUMBER = pygame.USEREVENT + 1
CLIENT_QUIT = pygame.USEREVENT + 2
# ...
class ConsoleInputThread( threading.Thread ):
""" A thread that handles user input on the console.
Waits for user input, then posts messages
to the main PyGame thread for processing """
def __init__( self, prompt ):
threading.Thread.__init__(self)
self.daemon = True # exit with parent
self.done = False
self.prompt = prompt
def stop( self ):
self.done = True
def run( self ):
""" Loops until the user hangs-up """
while ( not self.done ):
# Get some input from the user
user_input = input( self.prompt ).strip()
new_event = None
if ( user_input == 'quit' ):
new_event = pygame.event.Event( UserEvents.CLIENT_QUIT, { } )
else:
try:
user_input = int( user_input )
new_event = pygame.event.Event( UserEvents.CLIENT_NUMBER, { "value":user_input } )
except:
print( "Syntax Error" )
# If we received valid input post it to the main thread
if ( new_event ):
pygame.event.post( new_event )
###
### MAIN
###
# Create the window
pygame.init()
pygame.display.set_caption("Socket Messages")
SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE )
# Start the console-input thread
input_thread = ConsoleInputThread( "How many people would you like to have: " )
input_thread.start()
# Main paint / update / event loop
done = False
clock = pygame.time.Clock()
colour_index = 0
while ( not done ):
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == UserEvents.CLIENT_QUIT ):
print("\nCLIENT ASKED TO QUIT " )
done = True
elif ( event.type == UserEvents.CLIENT_NUMBER ):
print( "\nVALUE WAS INPUT: %d " % ( event.value, ) )
WINDOW.fill( colour_cycle[colour_index] )
# rotate the colours, just so the screen changes
colour_index += 1
if ( colour_index >= len( colour_cycle ) ):
colour_index = 0
pygame.display.flip()
clock.tick_busy_loop(2) # NOTE: 2 frames per second, no flashy-flashy
input_thread.stop()
pygame.quit()
I'm working on a game for class and this is what we've learned to do so far. I'm trying to get the sprites to stay onscreen with clamp_ip, but it keeps giving me an error message that "Butterfly instance has no attribute clamp_ip." Is there a different way I should be keeping the butterflies onscreen?
This first bit is just setting up pygame and the butterfly class (where I included a line to define a rectangle for the butterflies), I've highlighted where I'm guessing the potential errors are below.
This should be ok.
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
playground = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Butterflies')
screenRect = playground.get_rect()
steps = 1
class Butterfly:
def __init__(self):
self.x = random.randint(0, SCREEN_WIDTH)
self.y = random.randint(0, SCREEN_HEIGHT)
self.image_Butterfly = pygame.image.load('butterflya.png')
self.image_ButterflyB = pygame.image.load('butterflyb.png')
self.height = self.image_Butterfly.get_height()
self.width = self.image_Butterfly.get_width()
self.rect = (self.width, self.height)
clock = pygame.time.Clock()
fps = 10
net = []
for i in range (15):
butterflyInstance = Butterfly()
net.append(butterflyInstance)
playground.fill(cyan)
Game_Over = False
while not Game_Over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
Game_Over = True
This is where I'm guessing I messed up. I tried to use the clamp_ip function at the end of this loop
for butterflyInstance in net:
butterflyInstance.x += random.randint(-10, 10)
butterflyInstance.y += random.randint(-10,10)
if steps % 2 == 0:
playground.blit(butterflyInstance.image_Butterfly, (butterflyInstance.x, butterflyInstance.y))
steps += 1
else:
playground.blit(butterflyInstance.image_ButterflyB, (butterflyInstance.x, butterflyInstance.y))
steps +=1
butterflyInstance.clamp_ip(screenRect)
Thank you so much!
See PyGame doc pygame.Rect()
clamp_ip is pygame.Rect method so you have to use butterflyInstance.rect.clamp_ip()
But you have to use self.rect.x, self.rect.y, self.rect.width, self.rect.height instead of self.x, self.y, self.width, self.height to keep position and size of object.
And use butterflyInstance.rect.x, butterflyInstance.rect.y, butterflyInstance.rect.width, butterflyInstance.rect.height
instead of butterflyInstance.x, butterflyInstance.y, butterflyInstance.width, butterflyInstance.height
PyGame use pygame.Rect in many places - it is usefull. For example: you can use rect.right instead of rect.x + rect.width, or rect.center = screenRect.center to center object on screen.