需要帮助提高PyGame在点击方面的性能和响应能力(MOUSEBUTTONDOWN)



我正试图使用PyGame在python内部重新创建国际象棋,目前,我一直在研究能够移动棋子的机制。我选择从编写骑士的动作开始,因为它的动作是所有棋子中最复杂的。我已经成功地创建了一个系统,玩家可以选择棋子并相应地将其移动到网格上的任何位置。如果玩家最终决定不移动棋子,他们也可以取消选择棋子。我还没有对骑士移动的所有可能方式进行编码;只有几个;但这里的主要问题是反应极其缓慢。我的意思是,我必须点击多次才能将一件作品"选中"或取消选中,再点击多次才能移动到所需的位置。我认为这与在更新中调用一个函数有关,该函数涉及使用for循环循环遍历pygame的事件以检测鼠标按下事件,但我不完全确定。如有任何改进回复的帮助,我们将不胜感激!注意:我正在将棋子的代码导入主游戏程序,并将主代码导入Knight类中的select方法,以访问一些重要变量,如pGrid和方法,如player_updategrid()

主要游戏逻辑:

import pygame
import pieces
import itertools
import numpy as np

#2050/8 = 256.25
#2050/960 = 2.135
WINWIDTH = 960
WINHEIGHT = 960
WHITE = (200,200,200)
LIGHTTAN = (247, 245, 218)
BROWN = (158, 114, 73)
pygame.init()
win = pygame.display.set_mode((WINWIDTH,WINHEIGHT))
pygame.display.set_caption('Chess')
run = True
bPieces = pygame.sprite.Group()
wPieces = pygame.sprite.Group()
bKnight = pieces.knight(win, 7, 7)
bPieces.add(bKnight)
pgrid = []
#SET UP THE BOARD
board = np.ones((3,3))
board = np.zeros((8,8),dtype=int)
board[1::2,::2] = 1
board[::2,1::2] = 1
def bgupdategrid():
for row in range(0,8):
for column in range(0,8):
if board[row][column] == 1:
pygame.draw.rect(win, (LIGHTTAN),
(((column * (WINWIDTH) // 8)), ((row * (WINWIDTH) // 8))
, (WINWIDTH - 24) // 8, (WINHEIGHT - 24) // 8))
else:
pygame.draw.rect(win, (BROWN),
(((column * (WINWIDTH) // 8)), ((row * (WINWIDTH) // 8))
, (WINWIDTH - 24) // 8, (WINHEIGHT - 24) // 8))

def player_updategrid(pos = None):
for row in range(0,8):
pgrid.append([])
for column in range(0,8):
pgrid[row].append(0)
if pgrid[row][column] == 1 and pos == 'y':
return ((column * (WINWIDTH) // 8) + column * 2)
elif pgrid[row][column] == 1 and pos == 'x':
return ((row * (WINWIDTH) // 8) + row * 2)
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False

#UPDATE BOARD UI
bgupdategrid()


#UPDATE PIECE DISPLAY

bPieces.draw(win)
player_updategrid()
bPieces.update()
pygame.display.flip()
pygame.quit()

骑士碎片代码:

import pygame
class knight(pygame.sprite.Sprite):
def __init__(self, surface, x, y):
pygame.sprite.Sprite.__init__(self)
self.surface = surface
self.image = pygame.image.load("D:PythonProjectsChessAssets/blackKnight.png")
self.image = pygame.transform.scale(self.image, (120,120))
self.rect = pygame.Rect(x, y, 120,120)  #self.image.get_rect()
self.x = x
self.y = y
self.selected = False
def update(self):
import main as m
self.move()
m.pgrid[self.x][self.y] = 1
self.rect.x = m.player_updategrid('x')
self.rect.y = m.player_updategrid('y')

def getmouse(self):
getmouse = pygame.mouse.get_pos()
mposx = getmouse[0]
mposy = getmouse[1]
return mposx//120, mposy//120
def move(self):
import main as m
mposx, mposy = self.getmouse()
#print(self.selected)
if mposy == self.y and mposx == self.x and self.selected is False:
for event in pygame.event.get():
if self.selected == False:
if event.type == pygame.MOUSEBUTTONDOWN:
print('selected')
self.selected = True
elif mposy == self.y and mposx == self.x and self.selected == True:
#print('yoyoyoyo')
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
print('unselected')
self.selected = False
elif mposy != self.y and mposx != self.x and self.selected is True:
for event in pygame.event.get():
print(mposx, mposy)
if event.type == pygame.MOUSEBUTTONDOWN:
if (((self.x - 2) == mposx) and ((self.y - 1) == mposy)) or (((self.x - 2) == mposx) and ((self.y - 1) == mposy)):
#print('hi')
m.pgrid[self.x][self.y] = 0
self.x = mposx
self.y = mposy
self.selected = False

问题是您正在处理Knight.move()函数中Knight的用户输入事件。这是而不是应该去的地方。最好只有一个地方来处理用户输入,然后根据当时的游戏状态来决定输入需要去哪里。这个代码的一个好地方是主循环。

目前,事件只在调用Knight.move()函数的那一瞬间被检查,所以可能在的大部分时间里,事件都没有得到处理,这就是为什么很难点击的原因。你真的不希望玩家的输入处理分散在棋子类型的6种不同实现中。

所以。。。该代码需要进行一些重大的重组。但首先让我们添加一些实用功能:

添加第二个2D列表来保存所有作品的逻辑位置。它为空单元格保留None,或者如果该单元格被占用,则保留对Sprite对象(例如:Knight(的引用,怎么样。例如:

# Create an empty board
board_pieces = []
for i in range( 8 ):
board_pieces.append( [None] * 8 )

也许你已经有了,但我看不到。然后我们将编写一个函数,可以点击鼠标,并确定点击是否在一块上。但首先我们需要将鼠标坐标转换为棋盘坐标,然后再转换回来。

def windowToBoardCoord( win_x, win_y ):
""" Convert the window co-ordinates to board co-ordinates """
board_x = win_x // ( WINDOW_WIDTH  // 8 )
board_y = win_y // ( WINDOW_HEIGHT // 8 )
# Debugging, comment out later when it all works
print( "Window (%d,%d) => Board (%d,%d)" % ( win_x, win_y, board_x, board_y ) )
return board_x, board_y
def boardToWindowCoord( board_x, board_y ):
""" Convert the board co-ordinates to window co-ordinates """
# NOTE: returns the top-left corner
win_x = board_x * 8
win_y = board_y * 8
# Debugging, comment out later when it all works
print( "Board (%d,%d) => Window (%d,%d)" % ( board_x, board_y, win_x, win_y ) )
return win_x, win_y
def getPieceAt( board, board_x, board_y ):
""" Get the piece on the board, or None """
any_piece = board[board_y][board_x]    # could be None
# Debugging, comment out later when it all works
if ( any_piece == None ):
print( "No piece at board[%d][%d]" % ( board_y, board_x ) )
else:
print( "Piece [%s] is at board[%d][%d]here" % ( str( any_piece ), board_y, board_x  ) )
return any_piece

这使我们能够看到鼠标点击,获得鼠标位置,然后确定点击板的位置。

我们将添加一个名为current_selection的变量来保留当前选择的玩家片段,该片段最初为None

回到主事件循环:

current_selection = None     # any piece the player has selected
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEBUTTONDOWN:   # user clicked the mouse
mouse_x, mouse_y = event.pos             # where was it clicked
board_x, board_y = windowToBoardCoord( mouse_x, mouse_y )
# Is there a piece at those co-ordinates?
piece = getPieceAt( board_pieces, board_x, board_y )
# If the player has already selected a piece, this is a move
# to that new location
if ( current_selected != None ):
if ( piece == current_selected ):
# clicked on the same piece twice, un-select it
current_selected = None
else:
# This is a move, but is it valid?
if ( current_selected.isLegalMove( board_pieces, board_x, board_y )
# Valid move
# Update the board ( maybe this should be a function )
current_x, current_y = current_selected.getBoardPosition()
board[ current_y ][ current_x ] = None
board[ board_y ][ board_x ]     = current_selected
# Update the screen
current_selected.moveTo( board_x, board_y )
# ... handle taking pieces, etc. TODO  
elif ( piece != None ):
# Nothing selected, is this a new selection
if ( piece.isOwnedByPlayer() ):
current_selected = piece
else:
# can't select opponents pieces
makeErrorBeep()

#UPDATE BOARD UI
bgupdategrid()
...

这让骑士精灵变得简单多了。它只需要有一个imagerect,还需要实现这种类型工件的各种规则。

class Knight( pygame.sprite.Sprite ):
def __init__( self, x, y, colour ):
pygame.sprite.Sprite.__init__(self)
self.name = 'Knight'
# TODO: use os.path.join() here ~
self.image = pygame.image.load("D:PythonProjectsChessAssets/blackKnight.png")
self.image = pygame.transform.scale(self.image, (120,120))
self.rect  = self.image.get_rect()
self.rect.topleft = ( x, y )
def update(self):
# image does not change (yet)
pass
def moveTo( self, board_x, board_y ):
win_x, win_y = boardToWindowCoord( board_x, board_y )
self.rect.topleft = ( win_x, win_y )
def getBoardPosition( self ):
x, y = self.rect.topleft
board_x, board_y = windowToBoardCoord( x, y )
return board_x, board_y
def isLegalMove( self, board, move_x, move_y ):
""" Is it a legal move from current-position to (x,y) """
board_x, board_y = self.getBoardPostion()
# TODO: check all those L-shaped movements
#       Can we move from (board_x, board_y) to (move_x, move_y)
result = True   # TODO: just accept any movement for testing
return result

我建议您通过以下方式减少代码中嵌套循环的数量:

  • 只更新移动的工件的矩形(之前和之后(,因此您需要2个变量来存储所选工件的旧位置和新位置。以便在更新期间减少迭代
  • 删除你过去知道有哪种颜色的矩阵(如果你想设置它,你可以保留它,并放置其他块的位置,这样你就不会把一块放在另一块相同颜色的上面(,但要选择颜色,你可以使用位置(x和y(示例:if x+y %2 == 0,这意味着它是白色,else,然后它是另一种颜色
  • 如果你想避免在新旧职位上使用这两个变量,你可以使用董事会的图像,并将这些片段作为带有透明背景的PNG图像,将一个图像闪电式地覆盖在另一个图像上比在每个单元格的所有情况下循环要快得多
  • 降低执行速度的一件事是,您多次导入主循环,并在之前执行的循环之外重新启动了一个新循环(如果您的主游戏逻辑文件是main.py(

我希望它能有所帮助。一个提示:现在不要忘记添加友好片段的案例,以免重做部分代码

最新更新