快速重绘Tkinter窗口 - 有更快的方法吗?

1 投票
3 回答
4640 浏览
提问于 2025-04-16 09:09

我正在用Python制作一个微环境模拟器,目前我已经有了一个分形地图生成器和一个简单的移动“实体”的碰撞检测类。当画布上东西不多的时候,这些实体运行得很好,但现在每次更新都需要重新绘制地图,运行速度就慢了。我用create_rectangle来把实体矩阵显示在地图矩阵上(地图矩阵叫做“瓦片”)。

我试过使用pygame和其他一些模块,但无论用哪个版本的Python(甚至是旧的32位版本),都无法让它们正常工作。我觉得这可能是因为我在用64位的Windows 7,它们似乎不兼容。

总之,有没有什么办法可以更快地显示这两个矩阵,以便我能获得更高的帧率?

非常感谢,这里是我的代码(你可以用箭头键移动白色实体!!!):

from Tkinter import *
from random import *
from time import *

root = Tk()

w = Canvas(root, width = 640, height=640)
w.pack()

entities = []
running = True

## Size of squares, in pixels
size = 5

## Make tiles a 128x128 array
tiles = []
for i in range(128):
    tiles.append(128*[0])


## IMPORTANT: all entities must have .x and .y attributes!

class Entity:

    def __init__(self):
        self.setup()
        entities.append(self)

    def setup(self, x=0,y=0, name='Undefined', color='pink'):
        self.x = x
        self.y = y
        self.name = name
        self.color = color

    def checkCollsn(self, try_x , try_y):
        for i in entities:
            if i is not self and try_x == i.x and try_y == i.y:
                print i.name, 'is in the way of', self.name, '!'
                return False
        else:
            ## print 'All clear!'
            return True


    def maintain(self):

        for i in entities:
            if i is not self:
                ## Detect anything in immediate proximity:
                dx = abs(i.x - self.x)
                dy = abs(i.y - self.y)
                if (dx == 1 or dx == 0) and (dy == 1 or dy== 0):
                    print self.name, 'has detected', i.name
                    ## TODO: Then what?

        ## TODO: Add other 'maintainance checks' here.


class Mobile(Entity):  ## Mobile is a subclass of Entity, which can move!  
    def up(self):
        if self.y < 1:
            print 'Upper bound!'
        else:
            if self.checkCollsn(self.x, self.y-1):
                self.y -= 1

    def down(self):
        if self.y > 127:
            print 'Lower bound!'
        else:
            if self.checkCollsn(self.x, self.y+1):
                self.y += 1

    def left(self):
        if self.x < 1:
            print 'Leftmost bound!'
        else:
            if self.checkCollsn(self.x-1, self.y):
                self.x -= 1

    def right(self):
        if self.x > 127:
            print 'Rightmost bound!'
        else:
            if self.checkCollsn(self.x+1, self.y):
                self.x += 1


def fractalGen(iterations=5, chanceWater=0.5):
    for i in range(iterations):
        if i == 0:
            ## Generate random 16x16 blocks
            blockNo = 8
            blockLen = 16
            randomize = True
            fractalize = False
        else:
            randomize = False
            fractalize = True

        if i == 1:
            blockNo = 16
            blockLen = 8

        if i == 2:
            blockNo = 32
            blockLen = 4

        if i == 3:
            blockNo = 64
            blockLen = 2

        if i == 4:
            blockNo = 128
            blockLen = 1

        if i > 4:
            print 'Excessive i value! Was', i

        for yBlock in range(blockNo):
            ## If it stays red, something's screwy!
            blockVal = 0

            for xBlock in range(blockNo):
                if randomize:
                    ## print 'i:',i,'Randomizing.'
                    r = random()
                    if r <= chanceWater:
                        blockVal = 4
                    else: blockVal = 3

                if fractalize:
                    land = 0
                    water = 0
                    ## First, assess surrounding % water of all valid surrounding blocks.
                    ## Remember, blocks are now higher res than previous iteration!


                    xRange = [0]  ## This paragraph makes sure we're staying within
                    yRange = [0]  ## the range of the matrix.

                    if xBlock != 0:
                        xRange.append(-1)
                    if xBlock != blockNo-1:
                        xRange.append(1)

                    if yBlock != 0:
                        yRange.append(-1)
                    if yBlock != blockNo-1:
                        yRange.append(1)                    


                    for ySign in yRange:
                        yCheck = (yBlock+ySign)*blockLen
                        for xSign in xRange:
                            xCheck = (xBlock+xSign)*blockLen
                            if xSign ==0 and ySign == 0:
                                selfVal = tiles[yCheck][xCheck]
                                ## print 'Self is', selfVal
                            else:
                                if tiles[yCheck][xCheck] == 4:
                                    ## print 'Water at', xCheck, yCheck
                                    water += 1
                                if tiles[yCheck][xCheck] == 3:
                                    land += 1
                                    ## print 'Land at', xCheck, yCheck
                    percentWater = water/float(land+water)
                    ##print percentWater
                    if selfVal == 4: #If self is water, oppSurr is % land
                        oppSurr = 1 - percentWater
                    if selfVal == 3: #If self is land, oppSurr is % water
                        oppSurr = percentWater

                    r = randint(0,25)/100.0
                    ##print oppSurr, r, oppSurr + r

                    if oppSurr + r >= 0.625:
                    ## If surroundings + random > 50%, switch self type!
                        if selfVal == 4:
                            blockVal = 3
                            ## print 'To land'
                        elif selfVal == 3:
                            blockVal = 4
                            ## print 'To water'
                    ## else: print 'Not switched, remains', selfVal
                    else: blockVal = selfVal


                    ## NB: Must assign blockVal in all cases, otherwise
                    ## you get the last value of blockVal carried over!

                for y in range(yBlock*blockLen,(yBlock+1)*blockLen):
                    for x in range(xBlock*blockLen,(xBlock+1)*blockLen):
                        tiles[y][x] = blockVal


def drawWorld():

    w.delete(ALL)

    x=0
    y=0

    for i in tiles:
        for j in i:
            color = 'gray'
            if j == 0: tColor = 'red'
            if j == 1: tColor = 'orange'
            if j == 2: tColor = 'yellow'
            if j == 3: tColor = 'green'
            if j == 4: tColor = 'blue'
            if j == 5: tColor = 'purple'

            w.create_rectangle(x,y,x+size,y+size,fill=tColor)
            x += size
        x = 0
        y += size

    for i in entities:
        w.create_rectangle(i.x*size, i.y*size, i.x*size+size, i.y*size+size, fill=i.color)


    w.update()


fractalGen(5,.4)
drawWorld()

def keyPress(event):
    if event.keysym == 'Up':
        p.up()

    if event.keysym == 'Down':
        p.down()

    if event.keysym == 'Left':
        p.left()

    if event.keysym == 'Right':
        p.right()


def moveRand(ent):
    cmd = randint(0,3) 
    if cmd == 0: ent.up()
    if cmd == 1: ent.down()
    if cmd == 2: ent.left()
    if cmd == 3: ent.right()

## Player:
p = Mobile()
p.setup(0,5,'Man', 'white')

## Boulder:
b = Entity()
b.setup(10,15,'Boulder','black')

## Flower:
f = Entity()
f.setup(5,5,'Flower', 'red')

## Elf:
elf = Mobile()
elf.setup(10,10,'Elf','green')

interval = 0.2

while running:
        moveRand(elf)
        root.bind_all('<Key>', keyPress)

        for i in entities:
            i.maintain()

        drawWorld()

3 个回答

-1

一些最简单的修改:

from Tkinter import *
from random import *
from time import *

root = Tk()

w = Canvas(root, width = 640, height=640, bd=0, highlightthickness=0)
w.pack()

entities = []
running = True

## Size of squares, in pixels
size = 5

## Make tiles a 128x128 array
tiles = []
for i in range(128):
    tiles.append(128*[0])


## IMPORTANT: all entities must have .x and .y attributes!

class Entity:

    def __init__(self):
        self.setup()
        entities.append(self)

    def setup(self, x=0,y=0, name='Undefined', color='pink'):
        self.x = x
        self.y = y
        self.name = name
        self.color = color

    def checkCollsn(self, try_x , try_y):
        for i in entities:
            if i is not self and try_x == i.x and try_y == i.y:
                #print i.name, 'is in the way of', self.name, '!'
                return False
        else:
            ## print 'All clear!'
            return True


    def maintain(self):
        for i in entities:
            if i is not self:
                ## Detect anything in immediate proximity:
                dx = abs(i.x - self.x)
                dy = abs(i.y - self.y)
                if (dx == 1 or dx == 0) and (dy == 1 or dy== 0):
                    pass
                    #print self.name, 'has detected', i.name
                    ## TODO: Then what?

        ## TODO: Add other 'maintainance checks' here.


class Mobile(Entity):  ## Mobile is a subclass of Entity, which can move!  
    def up(self):
        if self.y < 1:
            self.y = 0
        else:
            if self.checkCollsn(self.x, self.y-1):
                self.y -= 1

    def down(self):
        if self.y > 127:
            self.y = 127
        else:
            if self.checkCollsn(self.x, self.y+1):
                self.y += 1

    def left(self):
        if self.x < 1:
            self.x = 0
        else:
            if self.checkCollsn(self.x-1, self.y):
                self.x -= 1

    def right(self):
        if self.x > 127:
            self.x = 127
        else:
            if self.checkCollsn(self.x+1, self.y):
                self.x += 1


def fractalGen(iterations=5, chanceWater=0.5):
    for i in range(iterations):
        if i == 0:
            ## Generate random 16x16 blocks
            blockNo = 8
            blockLen = 16
            randomize = True
            fractalize = False
        else:
            randomize = False
            fractalize = True

        if i == 1:
            blockNo = 16
            blockLen = 8

        if i == 2:
            blockNo = 32
            blockLen = 4

        if i == 3:
            blockNo = 64
            blockLen = 2

        if i == 4:
            blockNo = 128
            blockLen = 1

        if i > 4:
            print 'Excessive i value! Was', i

        for yBlock in range(blockNo):
            ## If it stays red, something's screwy!
            blockVal = 0

            for xBlock in range(blockNo):
                if randomize:
                    ## print 'i:',i,'Randomizing.'
                    r = random()
                    if r <= chanceWater:
                        blockVal = 4
                    else: blockVal = 3

                if fractalize:
                    land = 0
                    water = 0
                    ## First, assess surrounding % water of all valid surrounding blocks.
                    ## Remember, blocks are now higher res than previous iteration!


                    xRange = [0]  ## This paragraph makes sure we're staying within
                    yRange = [0]  ## the range of the matrix.

                    if xBlock != 0:
                        xRange.append(-1)
                    if xBlock != blockNo-1:
                        xRange.append(1)

                    if yBlock != 0:
                        yRange.append(-1)
                    if yBlock != blockNo-1:
                        yRange.append(1)                    


                    for ySign in yRange:
                        yCheck = (yBlock+ySign)*blockLen
                        for xSign in xRange:
                            xCheck = (xBlock+xSign)*blockLen
                            if xSign ==0 and ySign == 0:
                                selfVal = tiles[yCheck][xCheck]
                                ## print 'Self is', selfVal
                            else:
                                if tiles[yCheck][xCheck] == 4:
                                    ## print 'Water at', xCheck, yCheck
                                    water += 1
                                if tiles[yCheck][xCheck] == 3:
                                    land += 1
                                    ## print 'Land at', xCheck, yCheck
                    percentWater = water/float(land+water)
                    ##print percentWater
                    if selfVal == 4: #If self is water, oppSurr is % land
                        oppSurr = 1 - percentWater
                    if selfVal == 3: #If self is land, oppSurr is % water
                        oppSurr = percentWater

                    r = randint(0,25)/100.0
                    ##print oppSurr, r, oppSurr + r

                    if oppSurr + r >= 0.625:
                    ## If surroundings + random > 50%, switch self type!
                        if selfVal == 4:
                            blockVal = 3
                            ## print 'To land'
                        elif selfVal == 3:
                            blockVal = 4
                            ## print 'To water'
                    ## else: print 'Not switched, remains', selfVal
                    else: blockVal = selfVal


                    ## NB: Must assign blockVal in all cases, otherwise
                    ## you get the last value of blockVal carried over!

                for y in range(yBlock*blockLen,(yBlock+1)*blockLen):
                    for x in range(xBlock*blockLen,(xBlock+1)*blockLen):
                        tiles[y][x] = blockVal

def drawWorld():

    x=0
    y=0

    for i in tiles:
        for j in i:
            color = 'gray'
            if j == 0: tColor = 'red'
            if j == 1: tColor = 'orange'
            if j == 2: tColor = 'yellow'
            if j == 3: tColor = 'green'
            if j == 4: tColor = 'blue'
            if j == 5: tColor = 'purple'

            w.create_rectangle(x,y,x+size,y+size,fill=tColor)
            x += size

        x = 0
        y += size
    w.update()

def draw_entities():
    w.delete('ent')
    for i in entities:
        w.create_rectangle(i.x*size, i.y*size, i.x*size+size, i.y*size+size, fill=i.color, tags='ent')

def moveRand(ent):
    cmd = randint(0,3) 
    if cmd == 0: ent.up()
    if cmd == 1: ent.down()
    if cmd == 2: ent.left()
    if cmd == 3: ent.right()

def keyPress(event):
    if event.keysym == 'Up':
        p.up()

    if event.keysym == 'Down':
        p.down()

    if event.keysym == 'Left':
        p.left()

    if event.keysym == 'Right':
        p.right()

fractalGen(5,.4)
drawWorld()

## Player:
p = Mobile()
p.setup(0,5,'Man', 'white')

## Boulder:
b = Entity()
b.setup(10,15,'Boulder','black')

## Flower:
f = Entity()
f.setup(5,5,'Flower', 'red')

## Elf:
elf = Mobile()
elf.setup(10,10,'Elf','pink')

##interval = 0.2 #?

draw_entities()

root.bind_all('<Key>', keyPress)

try:
    while running:
        moveRand(elf)
        for i in entities:
            i.maintain()
        draw_entities()
        w.update()
except TclError:
    pass
-1

你可能已经发现Tk的局限性了,因为它并不是为了动画和其他复杂效果而设计的。

最好的选择是使用一个优化过的库,比如PyGame。它应该可以在Win 7 64位系统上运行——只要确保你安装的Python版本和PyGame匹配(通常是32位的Python)。

1

有几个方法可以提高你代码的帧率(FPS):

  1. 去掉w.update(),改用mainloop()这个正常的事件循环。
  2. 去掉w.delete(ALL),而是重复利用那些方块(只需移动它们,而不是删除再重新创建)。
  3. 利用canvas的find()方法来检测碰撞,而不是仅仅在Python代码里检查。
  4. 用Tcl重写代码,这样你就可以使用Tcl的线程功能,而不是受限于CPython的线程(如果你有多个CPU或核心,这样做会更有用)。;-)

撰写回答