【问题标题】:python turtle weird cursor jumppython turtle 奇怪的光标跳转
【发布时间】:2018-10-10 20:44:54
【问题描述】:

我正在尝试使用鼠标绘制海龟,我得到了下面的演示代码,但在鼠标移动期间有时光标会跳转:

#!/usr/bin/env python
import turtle
import sys

width = 600
height = 300
def gothere(event):
    turtle.penup()
    x = event.x
    y = event.y
    print "gothere (%d,%d)"%(x,y)
    turtle.goto(x,y)
    turtle.pendown()

def movearound(event):
    x = event.x
    y = event.y
    print "movearound (%d,%d)"%(x,y)
    turtle.goto(x,y)

def release(event):
    print "release"
    turtle.penup()

def circle(x,y,r):
    turtle.pendown() 
    turtle.goto(x,y)
    turtle.circle(r)
    turtle.penup()
    return

def reset(event):
    print "reset"
    turtle.clear()

#------------------------------------------------#
sys.setrecursionlimit(90000)
turtle.screensize(canvwidth=width, canvheight=height, bg=None)
turtle.reset()
turtle.speed(0)
turtle.setup(width, height)

canvas = turtle.getcanvas()

canvas.bind("<Button-1>", gothere)
canvas.bind("<B1-Motion>", movearound)
canvas.bind("<ButtonRelease-1>", release)
canvas.bind("<Escape>",reset)

screen = turtle.Screen()
screen.setworldcoordinates(0,height,width,0)
screen.listen()

turtle.mainloop()
#------------------------------------------------#

查看下面的 gif 以了解实际行为:

不确定是否有任何 API 调用错误!

【问题讨论】:

  • sys.setrecursionlimit(90000) 对我来说很可疑。如果您因为之前遇到异常错误且堆栈跟踪很长而添加了这一点,我怀疑该错误与您现在遇到的问题有关。
  • 看起来在海龟到达光标位置之前移动鼠标时会发生这种情况。当您单击时,您可以看到海龟向光标移动 - 如果您在鼠标到达光标之前开始移动鼠标,则在您拖动时它不会画一条线,一旦您松开鼠标按钮,您将得到那种奇怪的跳跃行为。
  • 如果我删除setrecursionlimit 行,那么这段代码偶尔会产生一个重复输入movearound 的回溯。我怀疑这是因为movearound 调用goto,后者调用update,它检查鼠标更新并可能再次调用movearound。如果 Tkinter 并不总是按照接收到鼠标事件的顺序评估鼠标事件,这可能解释了海龟的抖动运动。 effbot.org/tkinterbook/widget.htm 表示在回调中使用 update() 会导致“恶劣的竞争条件”,这似乎正是这里发生的情况。
  • (将上述评论作为评论而不是答案发布,因为我没有可以解决问题的快速解决方案;最好的办法就是不要在函数绑定到画布。但如果他不这样做,OP 将如何实现他想要的行为?)
  • 添加self.tracer(2,0) 似乎可以解决您的问题!

标签: python turtle-graphics


【解决方案1】:

添加turtle.tracer(2,0) 似乎使问题消失了。这可能是一个临时解决方案,因为我不知道它是否只是将问题隐藏在另一个问题之后。

...
screen = turtle.Screen()
screen.setworldcoordinates(0,height,width,0)
screen.listen()
turtle.tracer(2, 0)  # This line

turtle.mainloop()
...

在我们可以阅读的文档中:

打开/关闭海龟动画并设置更新图纸的延迟。

正如@Keven 指出的那样,问题似乎是回调中隐含的update。这将update 调用的数量减少了2。我认为它也可以删除递归调用。

注意:我不知道为什么,但删除 screen.setworldcoordinates(0,height,width,0) 会消除故障行为,但对 update 的递归调用仍然存在。

【讨论】:

    【解决方案2】:

    我发现您的代码存在几个问题:

    • 您正在将turtle 的面向对象接口与 该模块的功能接口。我推荐一个或 其他,但不是两者兼而有之。请参阅我的 import 更改以强制 OOP-only。

    • 您使用的是低级 tkinter 鼠标和键事件,而不是 乌龟自己的事件。我建议你尝试在海龟级别工作 (尽管与您的实现相比,这引入了一个小故障,请参阅 下面。)

    • 您通过不关闭事件引入了意外递归 在您的事件处理程序中。禁用这些处理程序中的事件 花费大量时间将清理您的图形。

    这是我对上述代码的修改。一个小故障是,与您原来的不同,“将海龟移动到此处”和“开始拖动”将需要两次单击,一次屏幕单击将海龟移动到当前位置,单击一次海龟开始拖动。这是由于 turtle 为 tkinter 事件提供的接口不同。 (Python 3 在这方面稍微好一点,但不适用于这种情况。)

    为了缓解这种情况,我使用了更大的海龟光标。我还添加了标题逻辑:

    from turtle import Turtle, Screen, mainloop
    
    WIDTH = 600
    HEIGHT = 300
    
    def gothere(x, y):
        screen.onscreenclick(gothere)  # disable events inside handler
    
        turtle.penup()
        print("gothere (%d,%d)" % (x, y))
        turtle.goto(x, y)
        turtle.pendown()
    
        screen.onscreenclick(gothere)
    
    def movearound(x, y):
        turtle.ondrag(None)  # disable events inside handler
    
        turtle.setheading(turtle.towards(x, y))
        print("movearound (%d,%d)" % (x, y))
        turtle.goto(x, y)
    
        turtle.ondrag(movearound)
    
    def release(x, y):
        print("release (%d,%d)" % (x, y))
        turtle.penup()
    
    def reset():
        print("reset")
        turtle.clear()
    
    screen = Screen()
    screen.setup(WIDTH, HEIGHT)
    # screen.setworldcoordinates(0, HEIGHT, WIDTH, 0)  # should work fine either way
    
    turtle = Turtle('turtle')
    turtle.speed('fastest')
    
    turtle.ondrag(movearound)
    turtle.onrelease(release)
    
    screen.onscreenclick(gothere)
    screen.onkey(reset, "Escape")
    
    screen.listen()
    
    mainloop()  # normally screen.mainloop() but not in Python 2
    

    但也请参阅this answer,我将展示如何使 tkinter 的 onmove 事件可用于海龟。

    ...“移动到这里”然后“开始拖动”的限制非常不 用户舒服吗?我们该如何改进呢?

    将我上面的代码与我链接到的替代答案结合起来,我们得到的解决方案与您开始的地方相似,但没有小故障,而且风格更像乌龟:

    from turtle import Turtle, Screen, mainloop
    from functools import partial
    
    WIDTH = 600
    HEIGHT = 300
    
    VERBOSE = False
    
    def onscreenmove(self, fun, btn=1, add=None):  # method missing from turtle.py
    
        if fun is None:
            self.cv.unbind('<Button%s-Motion>' % btn)
        else:
            def eventfun(event):
                fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
    
            self.cv.bind('<Button%s-Motion>' % btn, eventfun, add)
    
    def onscreenrelease(self, fun, btn=1, add=None):  # method missing from turtle.py
    
        if fun is None:
            self.cv.unbind("<Button%s-ButtonRelease>" % btn)
        else:
            def eventfun(event):
                fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
    
            self.cv.bind("<Button%s-ButtonRelease>" % btn, eventfun, add)
    
    def gothere(x, y):
    
        if VERBOSE:
            print("gothere (%d,%d)" % (x, y))
    
        turtle.penup()
        turtle.goto(x, y)
        turtle.pendown()
    
    def movearound(x, y):
    
        screen.onscreenmove(None)  # disable events inside handler
    
        if VERBOSE:
            print("movearound (%d,%d)" % (x, y))
    
    
        turtle.setheading(turtle.towards(x, y))
        turtle.goto(x, y)
    
        screen.onscreenmove(movearound)  # reenable events
    
    def release(x, y):
    
        if VERBOSE:
            print("release (%d,%d)" % (x, y))
    
        turtle.penup()
    
    def reset():
    
        if VERBOSE:
            print("reset")
    
        turtle.clear()
    
    screen = Screen()
    screen.setup(WIDTH, HEIGHT)
    screen.onscreenrelease = partial(onscreenrelease, screen)  # install missing methods
    screen.onscreenmove = partial(onscreenmove, screen)
    
    turtle = Turtle('turtle')
    turtle.speed('fastest')
    
    screen.onscreenclick(gothere)
    screen.onscreenrelease(release)
    screen.onscreenmove(movearound)
    
    screen.onkey(reset, "Escape")
    screen.listen()
    
    mainloop()  # normally screen.mainloop() but not in Python 2
    

    【讨论】:

    • 很好,但是“移动到这里”然后“开始拖动”的限制对用户来说非常不舒服?我们该如何改进呢?
    • @lucky1928,我通过修改我的代码来扩充我的答案,将一些缺失的 tkinter 事件添加到 turtle 以获得你想要的交互风格。
    【解决方案3】:

    我同意 cdlane 的观点,即尽可能避免 Tkinter 级别的事件绑定,但我认为在不改变整体设计的情况下解决问题也可能很有趣。我的解决方案只需要额外的导入:

    from collections import deque
    

    还有一个新版本的movearound

    pending = deque()
    def movearound(event):
        x = event.x
        y = event.y
        pending.append((x,y))
        if len(pending) == 1:
            while pending:
                x,y = pending[0]
                turtle.goto(x,y)
                pending.popleft()
    

    正如我在 cmets 中指出的,如果窗口的事件队列已备份,goto 可以调用 movearound,这可能会导致堆栈溢出或竞争条件,从而使海龟以不寻常的方式移动。这种方法旨在防止任意深度递归,只让movearound 的最顶层实例调用gotoif len(pending) == 1: 应该只对非递归调用成功;所有递归调用都会看到一个比这更大的队列。 while pending: 循环遍历所有已建立的事件,按照它们到达的顺序处理它们。

    结果:一只乌龟尽职尽责地跟随光标的路径,尽管以它自己的乌龟步伐:

    【讨论】:

    • 谢谢,太好了。你能把完整的代码贴在下面吗?
    猜你喜欢
    • 2018-06-13
    • 1970-01-01
    • 1970-01-01
    • 2011-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多