# update texture curtime = pg.time.get_ticks() if curtime >= next_tex_update: for x_ in range(streamtex.width // 4): for y_ in range(streamtex.height // 4): newcol = ( random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), 255, ) area = (4 * x_, 4 * y_, 4, 4) surf.fill(newcol, area) streamtex.update(surf) next_tex_update = curtime + tex_update_interval streamtex.draw(dstrect=pg.Rect(64, 128, 64, 64)) tex.draw(dstrect=(x, y)) # TODO: should these be? # - line instead of draw_line # - point instead of draw_point # - rect(rect, width=1)->draw 1 pixel, instead of draw_rect # - rect(rect, width=0)->filled ? , instead of fill_rect # # TODO: should these work with pg.draw.line(renderer, ...) functions? renderer.draw_color = (255, 255, 255, 255) renderer.draw_line((0, 0), (64, 64)) renderer.draw_line((64, 64), (128, 0)) renderer.draw_point((72, 32)) renderer.draw_rect(pg.Rect(0, 64, 64, 64))
def sprites(): pg.init() clock = pg.time.Clock() window = Window("Renderpyg Example", size=WINDOW_RESOLUTION) renderer = Renderer(window, vsync=True) """ We will draw into a buffer texture to allow easy resolution changes It will also make it easier to apply screen transitions and similar effects later When using pygame._sdl2.video you do not call pygame.display.setmode() Therefore calling surface.convert() or surface.convert_alpha() will throw an error When you create a Texture that needs alpha blending you must set its blend mode Alpha blending will be set automatically when loading from an image with transparency, such as PNG Remember to use the buffer size instead of the window size when drawing onto the offscreen buffer This will allow you to scale the screen to any window or fullscreen desktop size """ buffer = Texture(renderer, RENDER_RESOLUTION, target=True) buffer.blend_mode = 1 screensize = buffer.get_rect() """ You can set fullscreen when creating the window by using Window(title, size, desktop_fullscreen=True) I prefer creating a window before going to fullscreen to avoid strange window placement that occurs if you exit fullscreen later on. """ FULLSCREEN = False if FULLSCREEN: window.set_fullscreen(True) """ Font features in pygame are design for blitting to a surface, not for GPU rendering It is possible to create a streaming texture and then using texture.update() to update the texture from a pygame surface, but accessing GPU memory is slow and this should be done sparingly. Therefore I created a simple TextureFont class. We will use the animation feature of this class for a little extra fun. We will also create some sprites and let them animate too. Also, for this example we use a Character class to move and rotate individual characters across the screen. This is very similar to how you will handle sprites later. """ tfont = TextureFont(renderer, FONT, FONT_SIZE) sprite = Sprite((renderer, EXAMPLE_DATA + 'aliens.png'), 7, 8, by_count=True) group = pg.sprite.Group() animations = [ keyrange(0, 7, 200), keyrange(7, 14, 200), keyrange(14, 21, 200), keyrange(28, 35, 200) ] for _ in range(SPRITE_COUNT): spr = Sprite(sprite.images) spr.set_pos(randrange(0, RENDER_RESOLUTION[0]), randrange(0, RENDER_RESOLUTION[1])) spr.set_animation(random.choice(animations), -1) spr.velocity = pg.Vector2(randrange(-10, 11), randrange(-10, 11)) if randrange(10) < 2: spr.rotation = randrange(-10, 11) group.add(spr) """ Here starts a simple game loop Press SPACE to toggle between a large window, a small window, and fullscreen Press ENTER to add more characters to the screen At the beginning of each frame we must set the renderer target to our buffer Texture All the following draw calls will be drawn to the buffer instead of the screen After all of our drawing, we reset the target and draw the buffer onto the screen """ timer = pg.time.get_ticks() delta = 0 running = True while running: renderer.target = buffer for event in pg.event.get(): if event.type == pg.QUIT: running = False elif event.type == pg.KEYDOWN: if event.key == pg.K_ESCAPE: running = False elif event.key == pg.K_SPACE: if FULLSCREEN: FULLSCREEN = False window.size = WINDOW_RESOLUTION window.set_windowed() elif window.size == WINDOW_RESOLUTION: window.size = SMALL_RESOLUTION else: FULLSCREEN = True window.size = WINDOW_RESOLUTION window.set_fullscreen(True) #Must set the draw color before clearing the scren or drawing lines and rectt renderer.draw_color = (0, 0, 0, 255) renderer.clear() """ Draw the background image if available. By default Texture.draw() will fill the renderer unless you supply a destination Rect texture.draw( dstrect=Rect(x, y, width, height) ) """ group.update(delta) group.draw() tfont.animate(**FONT_PARAMS) # Setting renderer.target = None will make following draw calls render to the underlying window # Since we don't provide a dstrect it will fill the renderer renderer.target = None buffer.draw() renderer.present( ) # all draw calls occur and the screen is updated here delta = clock.tick(FRAMES_PER_SECOND)
def scale_tilemap(tilemap, camera=(0, 0), scale=1, **kwargs): ''' WIP smooth scaling tilemap renderer Still haven't gotten this working properly ''' return global buffer global printfo renderer = tilemap.images[1].texture.renderer old_viewport = renderer.get_viewport() old_target = renderer.target rend_width, rend_height = renderer.target.get_rect().size if \ renderer.target else old_viewport.size if not buffer: renderer.set_viewport(None) size = renderer.get_viewport().size buffer = Texture(renderer, size, target=True) buffer.blend_mode = 1 renderer.target = buffer renderer.clear() rendered_rect = renderer.get_viewport() if scale > 1: wanted_rect = scale_rect(rendered_rect, 1 / scale) src_rect = wanted_rect scale = 1 elif scale < 1: pass elif scale < 1: actual_scale = max(scale, .5) scale = 0.5 rect_scale = scale / actual_scale wanted_rect = scale_rect(rendered_rect, rect_scale) src_rect = wanted_rect printfo = (f'actual={actual_scale}, set={scale}, rect={rect_scale}, ' 'rect={src_rect}') elif False: tiles_wide = math.ceil(rend_width / (scale * tilemap.tilewidth)) adjusted_scale = (rend_width / tilemap.tilewidth) / tiles_wide buffer_scale = 1 - (scale - adjusted_scale) printfo = (f'base= {rend_width/tilemap.tilewidth}, tiles wide=' '{tiles_wide}, new_scale={buffer_scale}') wanted_rect = scale_rect(rendered_rect, buffer_scale) src_rect = wanted_rect src_rect.center = rendered_rect.center scale = adjusted_scale else: src_rect = rendered_rect if True: # center cam_x = (camera.x * scale) - (rend_width / 2) cam_y = (camera.y * scale) - (rend_height / 2) else: cam_x = camera.x * scale cam_y = camera.y * scale cell_count = len(tilemap.images) tile_w = int(tilemap.tilewidth * scale) tile_h = int(tilemap.tileheight * scale) cells_wide = rend_width // tile_w + 2 cells_high = rend_height // tile_h + 2 start_cellx, offset_x = divmod(int(cam_x), tile_w) start_celly, offset_y = divmod(int(cam_y), tile_h) offset_x = 0 offset_y = 0 percent = offset_x / tile_w if start_cellx < 0: # Handle negative values offset_x += start_cellx * tile_w start_cellx = 0 if start_celly < 0: offset_y += start_celly * tile_h start_celly = 0 dest_rect = pg.Rect(0, 0, tile_w, tile_h) layer = tilemap.layers[0].data ''' Render Tilemap ''' for row in layer[start_celly:start_celly + cells_high]: for frame in row[start_cellx:start_cellx + cells_wide]: if frame > 0: tilemap.images[frame].draw(dstrect=dest_rect) dest_rect.x += tile_w dest_rect.y += tile_h dest_rect.x = 0 renderer.target = old_target buffer.draw(srcrect=src_rect) ''' Cleanup ''' return cam_x, cam_y, scale
renderer.clear() # update texture curtime = get_ticks() if curtime >= next_tex_update: for x_ in range(streamtex.width // 4): for y_ in range(streamtex.height // 4): newcol = random.randint(0, 255), \ random.randint(0, 255), \ random.randint(0, 255), \ 255 area = (4 * x_, 4 * y_, 4, 4) surf.fill(newcol, area) streamtex.update(surf) next_tex_update = curtime + tex_update_interval streamtex.draw(dstrect=pygame.Rect(64, 128, 64, 64)) tex.draw(dstrect=(x, y)) #TODO: should these be? # - line instead of draw_line # - point instead of draw_point # - rect(rect, width=1)->draw 1 pixel, instead of draw_rect # - rect(rect, width=0)->filled ? , instead of fill_rect # # TODO: should these work with pygame.draw.line(renderer, ...) functions? renderer.draw_color = (255, 255, 255, 255) renderer.draw_line((0, 0), (64, 64)) renderer.draw_line((64, 64), (128, 0)) renderer.draw_point((72, 32)) renderer.draw_rect(pygame.Rect(0, 64, 64, 64))
def render_tilemap(tilemap, camera=(0, 0), scale=1, **kwargs): ''' Draw pytmx or inbuilt tilemap onto pygame GPU renderer :param tilemap: pytmx or internal tilemap class :param camera: pg.Vector2 for top left camera location :param scale: float scale value defaults to 1.0 :param center: pg.Vector2 to set center camera location True to adjust camera for center Overides camera :param srcrect: area to render in world coordinates overides scale Ignores height to maintain aspect ratio :param dstrect: screen area to render into defaults to entire renderer area :param smooth: for smoother scaling transition but less accurate :param clamp: True to adjust camera to fit world coordinates :rvalue (camx, camy, scale): for adjusting other images TODO: impliment smoothing ''' ''' Setup renderer and viewport ''' global buffer renderer = tilemap.images[1].texture.renderer old_viewport = renderer.get_viewport() old_target = renderer.target rend_width, rend_height = renderer.target.get_rect().size if\ renderer.target else old_viewport.size ''' Parse options ''' smooth = kwargs.get('smooth', False) clamp = kwargs.get('clamp', False) cam_x = int(camera.x) cam_y = int(camera.y) if smooth and scale != 1: if not buffer: renderer.set_viewport(None) size = renderer.get_viewport().size buffer = Texture(renderer, size, target=True) buffer.blend_mode = 1 renderer.target = buffer renderer.clear() dstrect = kwargs.get('dstrect', None) try: if dstrect: if not hasattr(dstrect, 'width'): dstrect = pg.Rect(*dstrect) renderer.set_viewport(dstrect) rend_width = dstrect.width rend_height = dstrect.height except: raise ValueError( 'render_tilemap() cannot parse "{}" as dest_rect'.format(dstrect)) srcrect = kwargs.get('srcrect', None) try: if hasattr(srcrect, 'width'): renderer.set_viewport(srcrect) camera = srcrect.pos scale = rend_width / srcrect.width center = False elif srcrect: srcrect = pg.Rect(srcrect) except: raise ValueError( 'render_tilemap() cannot parse "{}" as src_rect'.format(dstrect)) center = kwargs.get('center', False) try: if center and center != True: cam_x = int(center[0]) cam_y = int(center[1]) center = True except: raise ValueError( 'render_tilemap() cannot parse "{}" as center pos'.format(center)) if center: cam_x = (cam_x * scale) - (rend_width / 2) cam_y = (cam_y * scale) - (rend_height / 2) else: cam_x = cam_x * scale cam_y = cam_y * scale ''' Prepare to render the tilemap ''' cell_count = len(tilemap.images) tile_w = int(tilemap.tilewidth * scale) tile_h = int(tilemap.tileheight * scale) cells_wide = rend_width // tile_w + 2 cells_high = rend_height // tile_h + 2 if clamp: cam_x = min(max(0, cam_x), tile_w * tilemap.width - rend_width) cam_y = min(max(0, cam_y), tile_h * tilemap.height - rend_height) start_cellx, offset_x = divmod(int(cam_x), tile_w) start_celly, offset_y = divmod(int(cam_y), tile_h) if start_cellx < 0: # Handle negative values offset_x += start_cellx * tile_w start_cellx = 0 if start_celly < 0: offset_y += start_celly * tile_h start_celly = 0 dest_rect = pg.Rect(-offset_x, -offset_y, tile_w, tile_h) layer = tilemap.layers[0].data ''' Render Tilemap ''' for row in layer[start_celly:start_celly + cells_high]: for frame in row[start_cellx:start_cellx + cells_wide]: if frame > 0: tilemap.images[frame].draw(dstrect=dest_rect) dest_rect.x += tile_w dest_rect.y += tile_h dest_rect.x = -offset_x if smooth and scale != 1: renderer.target = old_target dest_rect.x = 0 dest_rect.y = 0 dest_rect.width = (tilemap.tilewidth * scale) * (rend_width // tilemap.tilewidth) dest_rect.height = (tilemap.tileheight * scale) * (rend_height // tilemap.tileheight) global printfo printfo = f'{dest_rect}, {tilemap.tilewidth*scale}, {cells_wide-2}' buffer.draw(srcrect=dest_rect) ''' Cleanup ''' return cam_x, cam_y, scale