快速重绘Tkinter窗口 - 什么是更快的方法?

时间:2010-12-29 02:40:36

标签: python tkinter

我正在尝试用Python制作一个微环境模拟器,到目前为止我有一个分形图生成器和一个简单的碰撞检测类移动'实体'。当画布稀疏时,实体工作正常,但现在它必须在每次更新时重绘地图,它运行缓慢。我正在使用create_rectangle来显示地图矩阵顶部的实体矩阵(它被称为'tiles')。

我已经尝试过使用pygame和其他一些模块,但我不能让它们与任何版本的python(甚至是旧的和32位版本)一起工作,我想这是因为我运行的是Windows 7 64-他们似乎不兼容。

无论如何,有没有办法更快地可视化这两个矩阵,以便我可以获得更高的FPS?

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

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 个答案:

答案 0 :(得分:1)

可能需要做一些事情来改善代码的FPS:

  1. 摆脱w.update()并通过mainloop()使用正常的事件循环
  2. 摆脱w.delete(ALL)并重复使用块(只需移动它们而不是删除它们并重新创建它们)
  3. 使用canvas find()方法的帮助进行碰撞检测,而不是仅在python代码中检查
  4. 在Tcl中重写,因此您可以使用Tcl的线程而不是残缺的CPython线程(仅在您拥有多个CPU /核心时才有用); - )

答案 1 :(得分:0)

你可能已经达到了Tk的极限 - 它不是为动画和其他花哨的步法而设计的。

最好的选择是优化的库,例如PyGame。应该没有理由不能在Win 7 64bit上运行 - 只要确保你有一个与PyGame匹配的Python(可能是32位Python)。

答案 2 :(得分:-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