backlinks to this page:
Github:
In this code example, the ball surface seems to move around. Keep in mind that the ball surface is not moving at all, it is just blitted to another position each frame (each cycle of the main loop). This movement is a frame-based movement and will depend on the actual frame-rate that your computer manages to display. Thus, the same pygame program will run slower on old or busy computers than on fast machines.
Manipulate the size of the pygame window (screen) or the workload of your computer (by moving big applications around) and you should notice that the ball needs a different amount of time to cross the screen.
The new variables dx and dy are variables to control the speed of the ball surface (in pixels per second). You could call (dx, dy) a speed vector. Because integer values would mean a minimum visible movement of one pixel per frame, dx and dy are decimal values (float). In the mainloop, the current movement is added to the current position (resulting in a float value). Because pygame is (yet) unable to blit at the position of a half-pixel, the position is rounded to integer values just for blitting.
#calculate new center of ball ballx += dx #bally += dy # y movement deactivated
# paint the ball screen.blit(ball, (round(ballx,0), round(bally,0)))
The pygame.display.set_caption line is too long to fit comfortably in this wiki. Here is the code line in its natural length:
pygame.display.set_caption("FPS: %.2f X: %.2f Y: %.2f dx: %.2f dy:%.2f" % (clock.get_fps(), ballx, bally, dx, dy))In Python, you can split a code line into several lines if:
result = 5 + 7 + \ + 10 print result # result is 5+7+10=22
If the ball (surface) hits the edge of the screen, the speed vector component dx or dy are inverted by multiplying with -1 to make sure that the ball surface position ballx or bally stay inside of the screen.
# bounce ball if out of screen if ballx < 0: ballx = 0 dx *= -1
You can draw on any pygame surface, even directly on the screen. While all changed surfaces need to be blitted again on the screen to make their changes visible, the screen surface is updated with the pygame.display.flip()
command each frame (main loop cycle).
Because the whole background is blitted on top of the screen (practically cleaning the screen) each frame, you are free to experiment with moving pictures.
Note that the random
module has to be imported at the start of the program.
import random # after importing pygame
The next code-lines draw an glittering, pulsating circle. add this code inside the mainloop:
# ----- pulsating circle ----------- colour = (random.randint(0,255),random.randint(0,255),random.randint(0,255)) if radius > 100 or radius < 5: dr *= -1 radius += dr pygame.draw.circle(screen, colour , (100,100), radius, 2) # draw pulsating circle # -------- end of pulsating circle -------
To run this example you need:
file | in folder | download | comment |
---|---|---|---|
005_frame_based_movement.py | pygame | Download the whole Archive with all files from Github: https://github.com/horstjens/ThePythonGameBook/archives/master | simple version |
005_frame_based_movement_pretty.py | pygame | pretty version |
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/005_frame_based_movement.py
If using Python 3, be sure to replace the division operators in the following line:
pygame.draw.circle(background, (0,0,200), (screenrect.width/2, screenrect.height/2), screenrect.width/3)
with the ”//“ operator to truncate the quotients and return integers, rather than floating point numbers. And remember that print is now a function.
#!/usr/bin/env python """ 005_bouncing_ball_frame_based.py bouncing ball and pulsating circle url: http://thepythongamebook.com/en:part2:pygame:step005 author: horst.jens@spielend-programmieren.at licence: gpl, see http://www.gnu.org/licenses/gpl.html works with python3.4 and python2.7 bouncing ball. each frame the complete screen is filled with the background, making this example simple to code but possible slow on larger resolutions. Each frame, a random-coloured circle is drawn with randomized radius directly on the screen. Try to manipulate the display.set_mode values to change the resolution.""" #the next line is only needed for python2.x and not necessary for python3.x from __future__ import print_function, division import pygame import random pygame.init() screen=pygame.display.set_mode((640,480),) # try out larger values and see what happens ! screenrect = screen.get_rect() # ------ constants ------------ clock = pygame.time.Clock() mainloop = True FPS = 30 # desired framerate in frames per second. playtime = 0 radius = 50 # for pulsating circle dr = 1 # change of radius in pixel per frame # ------- background --------- background = pygame.Surface(screen.get_size()) background.fill((255,155,155)) #fill the background white (red,green,blue) background = background.convert() screen.blit(background, (0,0)) #draw background on screen (overwriting all) # -------- bouncing ball surface --------- ballsurface = pygame.Surface((50,50)) #create a new surface (black by default) ballsurface.set_colorkey((0,0,0)) #make black the transparent color (red,green,blue) #pygame.draw.circle(Surface, color, pos, radius, width=0) pygame.draw.circle(ballsurface, (100,175,81), (25,25),25) # paint blue circle ballsurface = ballsurface.convert_alpha() # if you use tranparent colors you need convert_alpha() ballrect = ballsurface.get_rect() # the rectangle of the ball surface, for collision detection ballx, bally = 550, 240 # start position of the ball (x,y) dx = 10 # x speed vector of the ball in pixel per frame dy = 0 # y speed vector of the ball in pixel per frame # ----------- bouncing ball (drawing) ------ x1 = 50 y1 = 200 dx1 = 7 dy1 = 0 radius1 = 40 # --------- static big blue ball ----------- pygame.draw.circle(background, (0,0,200), (screenrect.width//2, screenrect.height//2), screenrect.width//3) # --------- mainloop ---------- while mainloop: # do all this each frame milliseconds = clock.tick(FPS) # do not go faster than this framerate playtime += milliseconds / 1000.0 for event in pygame.event.get(): if event.type == pygame.QUIT: mainloop = False # pygame window closed by user elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: mainloop = False # user pressed ESC pygame.display.set_caption("FPS: %.2f X: %.2f Y: %.2f dx: %.2f dy:" " %.2f" % (clock.get_fps(), ballx, bally, dx, dy)) # ----- clean screen ---------- screen.blit(background, (0,0)) #draw background on screen (overwriting all) # ------- bouncing ball (drawing) --------- x1 += dx1 if x1 + radius1 >= screenrect.width: x1 = screenrect.width - radius1 dx1 *= -1 elif x1 - radius1 <= 0: x1 = radius1 dx1 *= -1 pygame.draw.circle(screen, (255,255,0), (x1,y1), radius1) # -------- bouncing ball surface ---------- ballx += dx bally += dy if ballx < 0: # bounce ball if out of screen ballx = 0 dx *= -1 elif ballx + ballrect.width > screenrect.width: ballx = screenrect.width - ballrect.width dx *= -1 screen.blit(ballsurface, (round(ballx,0), round(bally,0))) # ----- pulsating circle ----------- colour = (random.randint(0,255),random.randint(0,255),random.randint(0,255)) if radius >100 or radius < 5: dr *= -1 radius += dr pygame.draw.circle(screen, colour , (100,100), radius, 2) # draw pulsating circle # --------- flip screen ------------------ pygame.display.flip() # flip the screen FPS times a second print("This 'game' was played for {:.2f} seconds.".format(playtime))
(by yipyip)
Note that this version move more objects than the simple version.
View/Edit/Download the file directly in Github:
#!/usr/bin/env python """ Name : blit_pulse2.py URL : http://thepythongamebook.com/en:part2:pygame:step003 Author : yipyip Licence: gpl, see http://www.gnu.org/licenses/gpl.html works with pyhton3.4 and python2.7 """ #### import pygame as pyg import random as rand #### def random_rgb(): return rand.randint(0, 255), rand.randint(0,255), rand.randint(0, 255) #### class PygView(object): def __init__(self, width=800, height=600, fps=50): """Initializing background surface for static drawing and screen surface for dynamic drawing """ pyg.init() pyg.display.set_caption("Press ESC to quit") self.width = width self.height = height self.screen = pyg.display.set_mode((self.width, self.height), pyg.DOUBLEBUF) self.background = pyg.Surface(self.screen.get_size()).convert() # white blackground self.background.fill((255, 255, 255)) self.act_surface = self.screen self.act_rgb = 255, 0, 0 def draw_static(self): self.act_surface = self.background def draw_dynamic(self): self.act_surface = self.screen def set_color(self, rgb): self.act_rgb = rgb def circle(self, x, y, radius, width): """Allocate surface for blitting and draw circle """ rad2 = 2 * radius surface = pyg.Surface((rad2, rad2)) pyg.draw.circle(surface, self.act_rgb, (radius, radius), radius, width) surface.set_colorkey((0, 0, 0)) self.act_surface.blit(surface.convert_alpha(), (x, y)) def run(self, draw_dynamic): """The mainloop """ running = True while running: for event in pyg.event.get(): if event.type == pyg.QUIT: running = False elif event.type == pyg.KEYDOWN: if event.key == pyg.K_ESCAPE: running = False draw_dynamic() pyg.display.flip() self.screen.blit(self.background, (0, 0)) pyg.quit() #### class Ball(object): """A circle object with no hardcoded dependency on pygame (and other libs too, obviously...) """ def __init__(self, x, y, radius, speed_x=1, speed_pulse=0, color=(0,0,255), width=0): self.x = x self.y = y self.radius = radius self.act_radius = radius self.speed_x = speed_x self.speed_pulse = speed_pulse self.color = color self.width = width self.shrinking = True @property def max_x(self): return self.x + self.radius * 2 def rel_move(self, dx, dy): self.x += dx self.y += dy def pulse(self): """Shrink or expand ball """ if not self.speed_pulse: return # balls are shrinking first if self.shrinking: if self.act_radius > self.width: self.act_radius -= self.speed_pulse self.act_radius = max(self.act_radius, self.width) else: self.shrinking = False else: if self.act_radius < self.radius: self.act_radius += self.speed_pulse else: self.shrinking = True def draw(self, view): """ Draw on a device with an appropriate interface """ if self.speed_pulse: color = random_rgb() else: color = self.color view.set_color(color) view.circle(self.x, self.y, self.act_radius, self.width) #### def action(balls, width, view): """ Return a function for the pygame mainloop """ # balls move to the right first right_moving = [True] * len(balls) def animate_balls(): """ Draw moving balls """ for i, ball in enumerate(balls): if right_moving[i]: if ball.max_x < width: ball.rel_move(ball.speed_x, 0) else: right_moving[i] = False else: if ball.x > 0: ball.rel_move(-ball.speed_x, 0) else: right_moving[i] = True ball.pulse() ball.draw(view) return animate_balls #### def main(width): """Simple example with stationary and moving balls """ view = PygView(width) view.draw_static() # args: x, y, radius, speed_x, speed_pulse, color, border_width # border_width <= radius ! ball01 = Ball(50, 60, 50, 0, 0, (255, 255, 0)) ball01.draw(view) ball02 = Ball(250, 150, 190, 0, 0, (66, 1, 166)) ball02.draw(view) view.draw_dynamic() ball1 = Ball(15, 130, 100, 1, 0, (255, 0, 0)) ball2 = Ball(25, 200, 80, 2, 0, (0, 255, 155)) ball3 = Ball(20, 220, 110, 1, 1, (100, 55, 155)) ball4 = Ball(20, 400, 70, 3, 0, (250, 100, 255)) ball5 = Ball(90, 390, 70, 0, 1, (250, 100, 255), 1) loopfunc = action((ball1, ball2, ball4, ball5), width, view) view.run(loopfunc) #### if __name__ == '__main__': main(900)