Есть ли эффективный способ сделать функцию перетаскивания нескольких PNG? - PullRequest
3 голосов
/ 11 июля 2019

Я играю в шахматы, но я полностью застрял на элементе перетаскивания, есть несколько направляющих, но все они либо перетаскивают фигуры, либо перетаскивают только одно изображение.

Я пробовал несколько вариантов кода, но все были более 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

Изображение просто не перетаскивается.

Ответы [ 2 ]

2 голосов
/ 11 июля 2019

Давайте пройдемся по этому шагу шаг за шагом.

Шаг 1: Давайте начнем с базового скелета каждой игры-пигмея:

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()

Мы создаем окно и затем запускаем цикл для прослушивания событий и рисования окна.

enter image description here

Пока все хорошо. Здесь нечего видеть, давайте двигаться дальше.


Шаг 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()

enter image description here


Шаг 3: Где находится мышь?

Нам нужно знать, какую часть мы хотим выбрать, поэтому нам нужно перевести экранные координаты ( где находится мышь относительно окна? ) в мир координаты (, на какой квадрат доски указывает мышь? ).

Таким образом, если доска не находится в начале (позиция (0, 0)), мы также должны принять во внимание это смещение .

По сути, мы должны вычесть это смещение (которое является позицией доски на экране) от позиции мыши (таким образом, у нас есть позиция мыши относительно доски), и разделить на размер квадратов.

Чтобы увидеть, работает ли это, давайте нарисуем красный прямоугольник на выделенном квадрате.

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()

enter image description here


Шаг 4: Давайте нарисуем несколько фигур

Шахматы скучны без каких-либо фигур, поэтому давайте создадим несколько фигур.

Я просто использую SysFont, чтобы нарисовать текст вместо реальных изображений, чтобы каждый мог просто скопировать / вставить код и сразу запустить его.

Мы храним кортеж (color, type) в списке вложенных 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):
    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()

enter image description here


Шаг 5: Drag'n'Drop

Для перетаскивания нам нужны две вещи:

  • мы должны изменить состояние вашей игры (перейдя в «режим перетаскивания»)
  • обработка событий для входа и выхода из «режима перетаскивания»

На самом деле все не так сложно. Чтобы войти в «режим перетаскивания», мы просто устанавливаем переменную (selected_piece), когда происходит событие MOUSEBUTTONDOWN. Поскольку у нас уже есть функция 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()

enter image description here

Конечно, есть много вещей, которые можно улучшить (например, использовать лучшие типы данных, чем кортежи, извлекать общую логику в функции и т. Д.), Но это должно дать вам хорошее начало для реализации таких вещей.

Всегда имейте в виду:

  • написать один игровой цикл, который обрабатывает события, игровую логику и рисование
  • убедитесь, что вы вызываете pygame.display.flip только один раз за кадр
  • отделить игровое состояние от функций рисования
  • никогда не звоните time.sleep или pygame.time.wait
  • используйте встроенные классы, такие как Vector2 и Rect, они облегчат вам жизнь (в этом коде я не использовал класс Sprite, но он также очень полезен)
  • используйте функции для очистки вашего кода
  • избегать глобальных переменных, кроме констант
1 голос
/ 11 июля 2019

PyGame - это библиотека низкого уровня, а не игровой движок, и вы должны сделать почти все с нуля.

В этом примере перетаскиваются два изображения, но для большего количества изображений можно использовать список или pygame.sprite.Group сpygame.sprite.Sprites, а затем код становится все длиннее и длиннее, но, как я уже сказал, PyGame не похож на игровой движок, т.е. Godot Engine (в котором используется язык, аналогичный Python).Может быть, с 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()

РЕДАКТИРОВАТЬ: то же самое с Group и Sprite, и теперь у вас естьчтобы добавить только изображения в группу 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) в левом нижнем углу.

Чтобы создать красный фон, он должен нарисовать прямоугольник (QUADS) в OpenGL.

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()
...