【问题标题】:Rapidly redrawing Tkinter window - What is a faster way?快速重绘 Tkinter 窗口 - 什么是更快的方法?
【发布时间】:2010-12-29 02:40:36
【问题描述】:

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

我尝试过使用 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()

【问题讨论】:

    标签: python tkinter


    【解决方案1】:

    可以做一些事情来提高代码的 FPS:

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • PyGame 不运行不应该是有原因的,但它仍然无法运行!我尝试了 32 位 Python 2.6.6,尽管安装了 pygame,但没有一个示例有效!例如,我得到: Traceback(最近一次调用最后一次):文件“C:\Python26\Lib\site-packages\pygame\examples\chimp.py”,第 18 行,在 main_dir = os.path.split (os.path.abspath(file))[0] NameError: name 'file' is not defined 为什么会这样?感谢您的建议!
      • 实际上,pygame 确实适用于我在网上找到的一些示例,但不适用于安装附带的示例(例如,上面的 chimp.py)!会发生什么?
      • 这不是 Tk 的限制,Tk 可以做一些动画的东西就好了,只需观看 Tcl'ers wiki 上的演示或游戏。例如链接:wiki.tcl.tk/898
      【解决方案3】:

      一些微小的变化:

      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
      

      【讨论】:

        猜你喜欢
        • 2020-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-02
        • 1970-01-01
        • 2020-04-13
        • 1970-01-01
        相关资源
        最近更新 更多