是否有一种有效的方法来制作拖放多个 png 的功能?

Is there an effiecient way of making a function to drag and drop multiple png's?

我正在制作一个国际象棋游戏,但我完全卡在了拖放元素上,那里有一些指南,但它们要么拖动形状,要么只拖动一个图像。

我已经尝试了几种代码变体,但所有代码都超过了 50 行,只是为了移动一个 .png,而且大多数都非常低效

pygame.init()

pygame.display.set_caption("Python Chess")

clock = pygame.time.Clock()
red = (213,43,67)
chew = pygame.image.load("chew.png")

gameDisplay.fill(red)
gameDisplay.blit(chew, (400, 400))
pygame.display.update()

drag = 0
if pygame.MOUSEBUTTONDOWN:
    drag = 1
if pygame.MOUSEBUTTONUP:
    drag = 0

gameExit = False

while not gameExit:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            gameExit = True

图片根本不拖。

PyGame是low-level库,不是游戏引擎,几乎所有东西都得从头开始。

这个例子拖了两张图片,但对于更多图片,它可以使用列表或 pygame.sprite.Grouppygame.sprite.Sprites,然后代码变得越来越长,但正如我所说,PyGame 不是游戏像引擎一样。 Godot Engine (which uses language similar to Python). Maybe with Pyglet 可能更容易,因为您不必从头开始编写 mainloop,但它仍然需要一些工作。在 PyGame 中,您甚至必须从头开始编写主要元素 - mainloop.

import pygame

# --- constants ---

RED = (213, 43, 67)

# --- main ---

pygame.init()
screen = pygame.display.set_mode((800,600))

chew1 = pygame.image.load("chew.png")
chew1_rect = chew1.get_rect(x=400, y=400)

chew2 = pygame.image.load("chew.png") # use different image
chew2_rect = chew1.get_rect(x=200, y=200)

drag = 0

# --- mainloop ---

clock = pygame.time.Clock()
game_exit = False

while not game_exit:

    # - events -
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_exit = True

        elif event.type == pygame.MOUSEBUTTONDOWN:
            drag = 1
        elif event.type == pygame.MOUSEBUTTONUP:
            drag = 0
        elif event.type == pygame.MOUSEMOTION:
            if drag:
                chew1_rect.move_ip(event.rel)
                chew2_rect.move_ip(event.rel)

    # - draws -            
    screen.fill(RED)
    screen.blit(chew1, chew1_rect)
    screen.blit(chew2, chew2_rect)
    pygame.display.update()

    # - FPS -
    clock.tick(30)

# --- end ---

pygame.quit()

编辑:GroupSprite 相同,现在您只需将图像添加到组 items 和其余部分代码不需要更改。

import pygame

# --- constants ---

RED = (213, 43, 67)

# --- classes ---

class Item(pygame.sprite.Sprite):

    def __init__(self, image, x, y):
        super().__init__()
        self.image = pygame.image.load(image)
        self.rect = self.image.get_rect(x=x, y=y)

    def update(self, rel):
        self.rect.move_ip(rel)

# --- main ---

pygame.init()
screen = pygame.display.set_mode((800,600))

items = pygame.sprite.Group(
    Item("chew.png", 200, 200),
    Item("chew.png", 400, 200), 
    Item("chew.png", 200, 400),
    Item("chew.png", 400, 400),
)

drag = 0

# --- mainloop ---

clock = pygame.time.Clock()
game_exit = False

while not game_exit:

    # - events -
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_exit = True
        elif event.type == pygame.MOUSEBUTTONDOWN:
            drag = 1
        elif event.type == pygame.MOUSEBUTTONUP:
            drag = 0
        elif event.type == pygame.MOUSEMOTION:
            if drag:
                items.update(event.rel)

    # - draws -            
    screen.fill(RED)
    items.draw(screen)
    pygame.display.update()

    # - FPS -
    clock.tick(30)

# --- end ---

pygame.quit()

编辑: 此版本使用组仅移动单击的图像。如果您在两个(或更多)图像重叠的地方单击,那么它将拖动两个(或更多)图像。

import pygame

# --- constants ---

RED = (213, 43, 67)

# --- classes ---

class Item(pygame.sprite.Sprite):

    def __init__(self, image, x, y):
        super().__init__()
        self.image = pygame.image.load(image)
        self.rect = self.image.get_rect(x=x, y=y)

    def update(self, rel):
        self.rect.move_ip(rel)

# --- main ---

pygame.init()
screen = pygame.display.set_mode((800,600))

items = pygame.sprite.Group(
    Item("chew.png", 150, 50),
    Item("chew.png", 400, 50), 
    Item("chew.png", 150, 300),
    Item("chew.png", 400, 300),
)

dragged = pygame.sprite.Group()

# --- mainloop ---

clock = pygame.time.Clock()
game_exit = False

while not game_exit:

    # - events -
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_exit = True
        elif event.type == pygame.MOUSEBUTTONDOWN:
            dragged.add(x for x in items if x.rect.collidepoint(event.pos))          
        elif event.type == pygame.MOUSEBUTTONUP:
            dragged.empty()
        elif event.type == pygame.MOUSEMOTION:
            dragged.update(event.rel)

    # - draws -            
    screen.fill(RED)
    items.draw(screen)
    pygame.display.update()

    # - FPS -
    clock.tick(30)

# --- end ---

pygame.quit() 

编辑: Pyglet 中的类似程序 - 它用更少的代码移动两个图像。

Pyglet 在左下角有点 (0,0)

要创建红色背景,必须在 OpenGL 中绘制矩形 (QUADS)。

import pyglet

window = pyglet.window.Window(width=800, height=600)

batch = pyglet.graphics.Batch()
items = [
    pyglet.sprite.Sprite(pyglet.resource.image('chew.png'), x=200, y=100, batch=batch),
    pyglet.sprite.Sprite(pyglet.resource.image('chew.png'), x=400, y=300, batch=batch),
]

@window.event
def on_draw():
    #window.clear()
    pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2f', [0,0, 800,0, 800,600, 0,600]), ('c3B', [213,43,67, 213,43,67, 213,43,67, 213,43,67])) #RED = (213, 43, 67)
    batch.draw()

@window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    for i in items:
        i.x += dx
        i.y += dy

pyglet.app.run()

让我们一步一步来完成。

第 1 步:让我们从每个 pygame 游戏的基本框架开始:

import pygame

def main():
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return
        screen.fill(pygame.Color('grey'))
        pygame.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()

我们创建一个window,然后开始一个循环来监听事件并绘制window。

到目前为止,还不错。没什么好看的,我们继续。


第 2 步:棋盘

所以,我们想要一个国际象棋游戏。所以我们需要一块板。我们创建了一个列表列表来表示我们的板,我们创建了一个 Surface 在屏幕上绘制我们的板。我们希望始终将游戏状态与实际绘图函数分开,因此我们创建了一个 board 变量和一个 board_surf.

import pygame

TILESIZE = 32

def create_board_surf():
    board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
    dark = False
    for y in range(8):
        for x in range(8):
            rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
            pygame.draw.rect(board_surf, pygame.Color('black' if dark else 'white'), rect)
            dark = not dark
        dark = not dark
    return board_surf

def create_board():
    board = []
    for y in range(8):
        board.append([])
        for x in range(8):
            board[y].append(None)
    return board

def main():
    screen = pygame.display.set_mode((640, 480))
    board = create_board()
    board_surf = create_board_surf()
    clock = pygame.time.Clock()
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return
        screen.fill(pygame.Color('grey'))
        screen.blit(board_surf, (0, 0))
        pygame.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()


第 3 步:鼠标在哪里?

我们需要知道我们想要哪一块select,所以我们必须平移屏幕坐标鼠标相对于window?)到世界坐标鼠标指向棋盘的哪个方格?)。

所以如果棋盘不在origin(位置(0, 0)),我们也得取这个offset考虑在内。

基本上,我们必须从鼠标位置中减去 offset(这是板在屏幕上的位置)(所以我们有鼠标相对于板的位置), 然后除以正方形的大小。

要查看这是否有效,让我们在 selected 正方形上绘制一个红色矩形。

import pygame

TILESIZE = 32
BOARD_POS = (10, 10)

def create_board_surf():
    board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
    dark = False
    for y in range(8):
        for x in range(8):
            rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
            pygame.draw.rect(board_surf, pygame.Color('black' if dark else 'white'), rect)
            dark = not dark
        dark = not dark
    return board_surf

def create_board():
    board = []
    for y in range(8):
        board.append([])
        for x in range(8):
            board[y].append(None)
    return board

def get_square_under_mouse(board):
    mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
    x, y = [int(v // TILESIZE) for v in mouse_pos]
    try: 
        if x >= 0 and y >= 0: return (board[y][x], x, y)
    except IndexError: pass
    return None, None, None

def main():
    screen = pygame.display.set_mode((640, 480))
    board = create_board()
    board_surf = create_board_surf()
    clock = pygame.time.Clock()
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return

        piece, x, y = get_square_under_mouse(board)

        screen.fill(pygame.Color('grey'))
        screen.blit(board_surf, BOARD_POS)

        if x != None:
            rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
            pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
        pygame.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()


第 4 步:让我们画一些棋子

国际象棋很无聊,没有棋子可以四处移动,所以让我们创造一些棋子吧。

我只是用 SysFont 画了一些文字,而不是用真实的图像,所以大家可以直接 copy/paste 代码和 运行 它。

我们在嵌套的 board 列表中存储一个元组 (color, type)。另外,让我们为我们的板使用一些其他颜色。

import pygame

TILESIZE = 32
BOARD_POS = (10, 10)

def create_board_surf():
    board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
    dark = False
    for y in range(8):
        for x in range(8):
            rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
            pygame.draw.rect(board_surf, pygame.Color('darkgrey' if dark else 'beige'), rect)
            dark = not dark
        dark = not dark
    return board_surf

def get_square_under_mouse(board):
    mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
    x, y = [int(v // TILESIZE) for v in mouse_pos]
    try: 
        if x >= 0 and y >= 0: return (board[y][x], x, y)
    except IndexError: pass
    return None, None, None

def create_board():
    board = []
    for y in range(8):
        board.append([])
        for x in range(8):
            board[y].append(None)

    for x in range(0, 8):
        board[1][x] = ('black', 'pawn')
    for x in range(0, 8):
        board[6][x] = ('white', 'pawn') 

    return board

def draw_pieces(screen, board, font):
    for y in range(8):
        for x in range(8): 
            piece = board[y][x]
            if piece:
                color, type = piece
                s1 = font.render(type[0], True, pygame.Color(color))
                s2 = font.render(type[0], True, pygame.Color('darkgrey'))
                pos = pygame.Rect(BOARD_POS[0] + x * TILESIZE+1, BOARD_POS[1] + y * TILESIZE + 1, TILESIZE, TILESIZE)
                screen.blit(s2, s2.get_rect(center=pos.center).move(1, 1))
                screen.blit(s1, s1.get_rect(center=pos.center))

def draw_selector(screen, piece, x, y):
    if piece != None:
        rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
        pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)

def main():
    pygame.init()
    font = pygame.font.SysFont('', 32)
    screen = pygame.display.set_mode((640, 480))
    board = create_board()
    board_surf = create_board_surf()
    clock = pygame.time.Clock()
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return

        piece, x, y = get_square_under_mouse(board)

        screen.fill(pygame.Color('grey'))
        screen.blit(board_surf, BOARD_POS)
        draw_pieces(screen, board, font)
        draw_selector(screen, piece, x, y)

        pygame.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()


第 5 步:拖放

对于拖放,我们需要两件事:

  • 我们必须更改您的游戏状态(进入 "drag-mode")
  • 进入和离开 "drag-mode"
  • 的事件处理

其实并没有那么复杂。要进入"drag-mode",我们只需在MOUSEBUTTONDOWN事件发生时设置一个变量(selected_piece)。由于我们已经有了get_square_under_mouse函数,所以很容易知道鼠标光标下是否真的有一个棋子。

如果设置了selected_piece,我们在鼠标光标下画一条线和棋子,并且在发生MOUSEBUTTONUP事件时跟踪光标下的当前方块。如果是这样的话,我们交换 board.

中棋子的位置
import pygame

TILESIZE = 32
BOARD_POS = (10, 10)

def create_board_surf():
    board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
    dark = False
    for y in range(8):
        for x in range(8):
            rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
            pygame.draw.rect(board_surf, pygame.Color('darkgrey' if dark else 'beige'), rect)
            dark = not dark
        dark = not dark
    return board_surf

def get_square_under_mouse(board):
    mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
    x, y = [int(v // TILESIZE) for v in mouse_pos]
    try: 
        if x >= 0 and y >= 0: return (board[y][x], x, y)
    except IndexError: pass
    return None, None, None

def create_board():
    board = []
    for y in range(8):
        board.append([])
        for x in range(8):
            board[y].append(None)

    for x in range(0, 8):
        board[1][x] = ('black', 'pawn')
    for x in range(0, 8):
        board[6][x] = ('white', 'pawn') 

    return board

def draw_pieces(screen, board, font, selected_piece):
    sx, sy = None, None
    if selected_piece:
        piece, sx, sy = selected_piece

    for y in range(8):
        for x in range(8): 
            piece = board[y][x]
            if piece:
                selected = x == sx and y == sy
                color, type = piece
                s1 = font.render(type[0], True, pygame.Color('red' if selected else color))
                s2 = font.render(type[0], True, pygame.Color('darkgrey'))
                pos = pygame.Rect(BOARD_POS[0] + x * TILESIZE+1, BOARD_POS[1] + y * TILESIZE + 1, TILESIZE, TILESIZE)
                screen.blit(s2, s2.get_rect(center=pos.center).move(1, 1))
                screen.blit(s1, s1.get_rect(center=pos.center))

def draw_selector(screen, piece, x, y):
    if piece != None:
        rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
        pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)

def draw_drag(screen, board, selected_piece, font):
    if selected_piece:
        piece, x, y = get_square_under_mouse(board)
        if x != None:
            rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
            pygame.draw.rect(screen, (0, 255, 0, 50), rect, 2)

        color, type = selected_piece[0]
        s1 = font.render(type[0], True, pygame.Color(color))
        s2 = font.render(type[0], True, pygame.Color('darkgrey'))
        pos = pygame.Vector2(pygame.mouse.get_pos())
        screen.blit(s2, s2.get_rect(center=pos + (1, 1)))
        screen.blit(s1, s1.get_rect(center=pos))
        selected_rect = pygame.Rect(BOARD_POS[0] + selected_piece[1] * TILESIZE, BOARD_POS[1] + selected_piece[2] * TILESIZE, TILESIZE, TILESIZE)
        pygame.draw.line(screen, pygame.Color('red'), selected_rect.center, pos)
        return (x, y)

def main():
    pygame.init()
    font = pygame.font.SysFont('', 32)
    screen = pygame.display.set_mode((640, 480))
    board = create_board()
    board_surf = create_board_surf()
    clock = pygame.time.Clock()
    selected_piece = None
    drop_pos = None
    while True:
        piece, x, y = get_square_under_mouse(board)
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return
            if e.type == pygame.MOUSEBUTTONDOWN:
                if piece != None:
                    selected_piece = piece, x, y
            if e.type == pygame.MOUSEBUTTONUP:
                if drop_pos:
                    piece, old_x, old_y = selected_piece
                    board[old_y][old_x] = 0
                    new_x, new_y = drop_pos
                    board[new_y][new_x] = piece
                selected_piece = None
                drop_pos = None

        screen.fill(pygame.Color('grey'))
        screen.blit(board_surf, BOARD_POS)
        draw_pieces(screen, board, font, selected_piece)
        draw_selector(screen, piece, x, y)
        drop_pos = draw_drag(screen, board, selected_piece, font)

        pygame.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()

当然还有很多可以改进的地方(比如使用比元组更好的数据类型,将通用逻辑提取到函数中等),但这应该让您在如何实现这些方面有一个良好的开端。

时刻牢记:

  • 编写一个处理事件、游戏逻辑和绘图的游戏循环
  • 确保每帧只调用一次pygame.display.flip
  • 将游戏状态与绘图函数分开
  • 从不调用 time.sleeppygame.time.wait
  • 使用 build-in class 等 Vector2Rect,它们会让您的生活更轻松(我没有 Sprite class 在此代码中,但它也非常有用)
  • 使用函数清理你的代码
  • 避免使用全局变量,常量除外