是否有一种有效的方法来制作拖放多个 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.Group
和 pygame.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()
编辑: 与 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)
。
要创建红色背景,必须在 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.sleep
或 pygame.time.wait
- 使用 build-in class 等
Vector2
和 Rect
,它们会让您的生活更轻松(我没有 Sprite
class 在此代码中,但它也非常有用)
- 使用函数清理你的代码
- 避免使用全局变量,常量除外
我正在制作一个国际象棋游戏,但我完全卡在了拖放元素上,那里有一些指南,但它们要么拖动形状,要么只拖动一个图像。
我已经尝试了几种代码变体,但所有代码都超过了 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.Group
和 pygame.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()
编辑: 与 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)
。
要创建红色背景,必须在 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.sleep
或pygame.time.wait
- 使用 build-in class 等
Vector2
和Rect
,它们会让您的生活更轻松(我没有Sprite
class 在此代码中,但它也非常有用) - 使用函数清理你的代码
- 避免使用全局变量,常量除外