如何截取pygame屏幕?

17 投票
4 回答
28156 浏览
提问于 2025-04-16 18:07

我该如何捕捉并保存pygame屏幕上的一系列图片或视频呢?
简单来说,我想把我的游戏视频分享在YouTube上,还想制作一个教程。

这个游戏主要是在一个循环中运行的:

def main():
    while True:
        GetInput()
        Move()
        Shift()
        Draw()

在这个循环中,Draw()函数负责处理所有的blit()操作,然后再执行pygame.display.flip()

4 个回答

1

当然可以!请看下面的内容:

在编程中,有时候我们需要让程序在特定的条件下执行某些操作。这就像给程序设定了一些规则,只有当这些规则被满足时,程序才会继续运行。比如说,如果你想让程序在用户输入正确的密码后才能进入系统,你就需要设置一个条件来检查这个密码。

此外,程序还可以根据不同的情况做出不同的反应。这就像是你在生活中遇到不同的情况时会有不同的选择。例如,如果天气很好,你可能会选择去外面玩;但如果下雨了,你可能会选择待在家里看电影。在编程中,我们可以使用“如果...那么...”这样的结构来实现类似的逻辑。

总之,编程就是在给计算机下达指令,让它根据我们的要求去做事情。通过设置条件和规则,我们可以让程序更加智能和灵活。

希望这些解释能帮助你更好地理解编程的基本概念!

x3 = pygame.surfarray.pixels3d(screen)
x3 = x3[:,:,::-1] 
3

被接受的答案提到你可以用 pygame.image.save 方法来保存当前的屏幕。

这个主意不错,不过在游戏运行的时候,你可能不想把图片保存到硬盘上,这个问题也被提到了。相反,你应该在程序中保存屏幕,然后在游戏停止后再处理这些屏幕。

下面是我的代码,里面有注释,主要展示了屏幕录制的基本思路。它使用了 opencv(配合 numpy)和 pygame,但你需要安装 ffmpeg 来把图片转换成视频(可以在终端试试 ffmpeg 命令)。不要让程序运行太久,因为保存的过程还是需要一些时间,而且大约和录制的帧数成正比。为了提高效率,你可以只录制每隔一帧的画面。

from contextlib import contextmanager
import os
import time

import cv2
import numpy as np
import pygame as pg
from pygame import surfarray

def pg_to_cv2(cvarray:np.ndarray)->np.ndarray:
    cvarray = cvarray.swapaxes(0,1) #rotate
    cvarray = cv2.cvtColor(cvarray, cv2.COLOR_RGB2BGR) #RGB to BGR
    return cvarray

def timer_wrapper(func):
    def inner(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        #print("Finished:" ,func.__name__ ,end-start)
        return end - start
    return inner

@contextmanager
def video_writer(*args,**kwargs):
    video = cv2.VideoWriter(*args,**kwargs)
    try:
        yield video
    finally:
        video.release()

@timer_wrapper
def save_frames(frames: list, average_dt: float|list, file_type: str = "mp4", name: str = "screen_recording"):
    if type(average_dt) is list: average_dt = sum(average_dt)/len(average_dt) # force average_dt to be a float
    size = frames[0].get_size()
    codec_dict={
        "avi":'DIVX',
        "mp4":'MP4V'
    }
    codec = cv2.VideoWriter_fourcc(*codec_dict[file_type])
    with video_writer(name+"."+file_type, codec, 1000/average_dt, size) as video: # file_name, codec, average_fps, dimensions
        for frame in frames:
            try:
                pg_frame = surfarray.pixels3d(frame) # convert the surface to a np array. Only works with depth 24 or 32, not less
            except:
                pg_frame = surfarray.array3d(frame) # convert the surface to a np array. Works with any depth
            cv_frame = pg_to_cv2(pg_frame)  # then convert the np array so it is compatible with opencv
            video.write(cv_frame)   #write the frame to the video using opencv

def draw_fps(s:pg.Surface,clock:time.Clock): 
    fps = clock.get_fps()
    sysfont.render_to(s,(100,100),str(fps),fgcolor=nice_green)

# initializing globals (colors, fonts, window, etc.)
pg.init()
sysfont = pg.freetype.SysFont(None,40)
BLACK = (0,)*3
nice_green = pg.Color("chartreuse2")
size=(1000, 600)
pg.display.set_caption("Screen Recording")
window = pg.display.set_mode(size)
# this is to save the frames
frames = []
dts = []
clock = pg.time.Clock()

running=True
try:
    while running:
        dt = clock.tick(60) # aim for ... fps
        for event in pg.event.get():
            if event.type == pg.QUIT:
                running=False
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    running=False
        window.fill(BLACK)
        draw_fps(window,clock)
        window_copy = window.copy() # if we don't copy the window then we will have the same image in all frames at the end
        frames.append(window_copy) # We save the current frame together with the time passed between the frames
        dts.append(dt)
        pg.display.flip()
        #if len(frames) >= 100: running = False # uncomment this to stop the game after ... frames for similar results in every run"
finally:
    pg.quit()
    # At this stage, the game ended and the cleanup process can start. For this we convert the frames to opencv images
    # Only then we will write the video to the hard drive (That is what makes the save so slow).
    # General information about the recording
    frame_num = len(frames)
    dt_sum = sum(dts)
    average_dt = dt_sum/frame_num
    # This is only an approximation: 
    # for each frame we have width * height many pixels -> frame_num * width * height
    # A Surface needs get_bytesize() many bytes per pixel (In this case 4 bytes, because we set the depth of the display to 32 bits)
    memory_usage_approx = frame_num * size[0] * size[1] * frames[0].get_bytesize()  #https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_bytesize
    print("Total time:" , dt_sum/1000,"s")
    print("Average time per frame:" , average_dt,"ms")
    print("Number of frames:", frame_num)
    print("Memory usage approximation" , memory_usage_approx/1000, "KB")
    args = (frames,dts,"avi","screen_recording")
    time_for_save = save_frames(*args)
    file_name = args[3]+"."+args[2]
    video_memory_usage = os.path.getsize(file_name)
    print("Video memory usage:" , video_memory_usage/1000, "KB")
    with open("test.txt", "a") as f:
        print("Total time:" , dt_sum/1000,"s\nNumber of frames:", frame_num,"\nSize:",size,"\nTime for save:",time_for_save,"s\nSaved in file:",file_name,file=f)
        print("_"*100,file=f)

或者你可以直接使用一个库,比如 Pygame Recorder

48

在你的屏幕表面上使用 pygame.image.save 来保存图像:

window = pygame.display.set_mode(...)

...

pygame.image.save(window, "screenshot.jpeg")

需要注意的是,这样做会让你的程序变得非常慢。如果你的程序是基于时间的,可能在捕捉图像时需要伪装一下帧率。

撰写回答