当前位置:首页 » tkinter » 正文

tkinter的Canvas组件,讲解演示动画知识初步

动画基本方法:

Canvas组件,我们可以画线,画圆,绘图等,怎样让这些线,圆,图这些画布的组件动起来呢,目前有3个方法:coords() , itemconfig() , move() ,下面,我就来讲解一下这3种方法。

 

move ( 对象ID , X方面移动距离,  Y方向移动距离)        

对象ID:当你在画布上创建线,圆,图等对象时,系统会为这些对象分配一个ID号,第一个创建的对象分配的ID号是1,第2个创建的对象分配的ID号是2,以此类推。 

from tkinter import *
import time
 
root=Tk()
 
can1 = Canvas(root,width=300, height=200,background='white') # 白底画布
can1.pack(fill=BOTH,expand=True)
 
can1.create_line(60,20,200,20) # 画线
can1.create_oval(60,50,80,70,outline='red',fill='blue') # 画圆
img1=PhotoImage(file='16-1.png')
can1.create_image(60,120,image=img1) # 绘图
 
for i in range(50):    # 移动50步
    can1.move(1,2,3)   # ID为1,每步:X方向右移动2像素,Y方向不移动
    can1.move(2,2,3)  # ID为2,每步:X方向左移动2像素,Y方向不移动
    can1.move(3,2,3)   # ID为3,每步:X方向移动2像素,Y像素移动3像素
    # can1.move(1,2,0)    # ID为1,线,向左移动
    # can1.move(2,-2,0)   # ID为2,圆,向右移动
    # can1.move(3,-2,-3)  # ID为3,图,向右上角移动
    can1.update()     # 刷新画布
    time.sleep(0.05)  # 时间暂停
 
root.mainloop()

 上面的代码,在画布上创建3个对象:线,圆,图片(它们的ID号,系统会根据创建时间的先后,分配的ID号分别是123),然后通过循环50次,让3个对象,在每一次循环时,X方面向左移动2个像素,Y方向向下移动3个像素。

 

运行结果:

   11.PNG

 

在用move() 方法移动画布上的对象后,一定要加上下面2句代码:

can1.update() # 不加这条代码,画布不刷新,我们看不到对象的移动

time.sleep(0.05) # 不暂停一下,我们的眼睛看不到移动过程,由于移动速度太快,只要看到对象最终的移动位置。

 

移动方向说明:

move ( 对象ID , X方面移动距离,  Y方向移动距离)

 

X方面移动距离:为正值是向右移动,为负值是向左移动,为0不移动

Y方面移动距离:为正值是向下移动,为负值是向上移动,为0不移动

 

 

所以,上面的代码,也可以让线向右移动,圆向下移动,QQ图向右上角方向移动。只要代码改成:

can1.move(1,2,0)    # ID为1,线,向左移动
can1.move(2,-2,0)   # ID为2,圆,向右移动
can1.move(3,-2,-3)  # ID为3,图,向右上角移动

我们也可以为对象ID号起个自己满意的名字,只要在画面上创建线,圆,图片时,赋值给一个变量,这个变量就是这个对象的ID号,下面以代码来说明,下面的代码,我在画线,画圆,绘图的创建代码中,我把返回值分别赋值给AAABBBCCC,这样,在后的move()方法中,我们就可以用AAA代替1这个ID号,BBB代替2这个ID号,CCC代替3这个ID号(当然我们也可以继续沿用以后的数字ID号)。

from tkinter import *
import time
 
root=Tk()
 
can1 = Canvas(root,width=300, height=200,background='white') # 白底画布
can1.pack(fill=BOTH,expand=True)
 
AAA=can1.create_line(60,20,200,20) # 画线
BBB=can1.create_oval(60,50,80,70,outline='red',fill='blue') # 画圆
img1=PhotoImage(file='16-1.png')
CCC=can1.create_image(60,120,image=img1) # 绘图
 
for i in range(50):    # 移动50步
    can1.move(AAA,2,3)   # ID为1,每步:X方向右移动2像素,Y方向不移动
    can1.move(BBB,2,3)  # ID为2,每步:X方向左移动2像素,Y方向不移动
    can1.move(CCC,2,3)   # ID为3,每步:X方向移动2像素,Y像素移动3像素
    can1.update()     # 刷新画布
    time.sleep(0.05)  # 时间暂停
 
root.mainloop()


下面我来讲一下coords()方法:

语法:coords(ID号,坐标值)

 

当没有坐标值这参数时,返回值是画布对象的坐标值,但是要注意,返回的坐标值对于不同的画布对象,返回的坐标值是不一样的。返回的坐标值是一个元组值,但返回的元组的元素不一样,有的是2个元素,有的是4个元素。

比如:上面的代码,我在最后加入代码:

print(can1.coords(AAA)) # 返回线的坐标
print(can1.coords(BBB)) # 返回圆的坐标
print(can1.coords(CCC)) # 返回图的坐标

运行后,输出:

[160.0, 170.0, 300.0, 170.0]

[160.0, 200.0, 180.0, 220.0]

[160.0, 270.0]

 

可以看出,图的坐标是2个元素的元组,而线,圆的坐标是4个元素的元组,这其实好理解,也好记忆,因为在画布上创建它们时,他们的坐标就分别是4个元素或2个元组。

 

如果在 coords(ID号,坐标值) 2个参数都提供,那么可以移动画布对象到坐标值的位置,这个坐标值参数提供的个数跟它们在画布创建时提供的坐标个数是一致的,像绘制线条,绘制圆就要提供 x1,y1,x2,y3 4个坐标参数,而在画布上插入图片则只要提供 x1,y1 2个坐标参数就行了。

 

下面的代码,用 coords()方法实现跟move()方法一样的效果。

from tkinter import *
import time
 
root=Tk()
 
can1 = Canvas(root,width=300, height=200,background='white') # 白底画布
can1.pack(fill=BOTH,expand=True)
 
AAA=can1.create_line(60,20,200,20) # 画线
BBB=can1.create_oval(60,50,80,70,outline='red',fill='blue') # 画圆
img1=PhotoImage(file='16-1.png')
CCC=can1.create_image(60,120,image=img1) # 绘图
 
for i in range(50):    # 移动50步
    wz1=can1.coords(AAA) # 获取线条目前的位置(x1,y1,x2,y2)
    can1.coords(AAA,wz1[0]+3,wz1[1]+3,wz1[2]+3,wz1[3]+3)
    # 在线条坐标的基础上,分别+3个像素,让线条向右下角移动
 
    wz2=can1.coords(BBB) # 获取小球目前的位置(x1,y1,x2,y2)
    can1.coords(BBB,wz2[0]+3,wz2[1]+3,wz2[2]+3,wz2[3]+3)
    # 在小球坐标的基础上,分别+3个像素,让小球向右下角移动
 
    wz3=can1.coords(CCC) # 获取图片目前的位置(x1,y1)
    can1.coords(CCC,wz3[0]+3,wz3[1]+3)
    # 在图片坐标的基础上,分别+3个像素,让图片向右下角移动
 
    can1.update()     # 刷新画布
    time.sleep(0.05)  # 时间暂停
 
root.mainloop()


运行结果是一样的。运行结果:

202111151542442469369.png

 

上面的代码是通过循环让3个画布对象向右下角方向移动,下面我来讲一下如何用鼠标移动这3个画布对象。

 

首先要遇到的问题是,我们如何指定要操作的画布对象呢?Canvas组件提供了几种方法让我们来指定要操作的画布对象:

1.  对象ID:如系统自动分配的ID123 还有我们自定义的IDAAA,BBB,CCC

2.  tags 为画布对象起的标签名。(下面我会讲解)

3.  ‘all’ :   表示Canvas组件中的所有画布对象,系统内置标签

4.  ‘current’ :  表示鼠标指针下的画布对象,,系统内置标签

 

其次要遇到问题是,我们如何操作这些画布对象呢?Canvas组件提供了跟组件事件绑定一样的方法:tags_bind(),方法里的参数跟组件的事件绑定类似,看下面的代码,你一看便知。

can1.tag_bind(AAA,"<B1-Motion>",test1)

这条代码,第1个参数,可以用AAA来指定操作的画布对象,也可以用ID号:1来指定操作对象,如果在创建画布对象时,为画布对象指定了一个标签tags,我们也可以用标签来代替AAA,由于是用鼠标点击拖动这个对象,所以,我们也可以用系统内置标签‘current’来代替。

 

下面的代码,我以上面的代码为基础,在创建画布对象时,我们加入参数tags,分别为3个画布对象起了3个标签:’t1’ , ‘t2’, ‘t3’

 
from tkinter import *
 
root=Tk()
 
can1 = Canvas(root,width=300, height=200,background='white') # 白底画布
can1.pack(fill=BOTH,expand=True)
 
AAA=can1.create_line(60,20,200,20,tags='t1') # 画线 加入tags参数
BBB=can1.create_oval(60,50,80,70,outline='red',fill='blue',tags='t2') # 画圆
img1=PhotoImage(file='16-1.png')
CCC=can1.create_image(60,120,image=img1,tags='t3') # 绘图
 
def test1(event):
    can1.coords(AAA,event.x-70,event.y,event.x+70,event.y)  # 用AAA指定对象
 #  can1.coords(1,event.x-70,event.y,event.x+70,event.y)    # 用1指定对象也可以
 #  can1.coords('t1',event.x-70,event.y,event.x+70,event.y) # 用't1' 指定对象也可以
def test2(event):
    can1.coords(BBB,event.x-10,event.y-10,event.x+10,event.y+10) # 用coords方法让对象跟鼠标坐标一同变化
def test3(event):
    can1.coords(CCC,event.x,event.y) # 图片坐标只有2个参数,上面的线,小圆有4个参数
 
can1.tag_bind(AAA,"<B1-Motion>",test1) # 线条 鼠标按下移动事件 绑定函数test1
can1.tag_bind(BBB,"<B1-Motion>",test2) # 小圆 鼠标按下移动事件 绑定函数test2
can1.tag_bind(CCC,"<B1-Motion>",test3) # 图片 鼠标按下移动事件 绑定函数test3
 
root.mainloop()

 上面代码,3个画布对象分别绑定“按住鼠标左键移动”事件,在绑定的函数里,用coords( 画布对象ID,对象要移动的目的坐标 ) 来移动画布对象。

 

运行后,用鼠标按住一个对象,可以把对象拖动到画布上的任何一个位置。

202111151546159197951.png

 

下面我来讲一个画布反弹小球程序,就是一个小球在画布不断移动,到达画布边缘,就像撞墙一样,小球会反弹。下面的代码没有新知识,都是move()方法,coords()方法 等知识的运用。

from tkinter import *
import time
 
def ok(): # 点击按钮,由yn变量决定是暂停还是继续动起来
    global yn
    if yn == True:
        yn = False
        but1['text']='  动起来  '
    else:
        yn = True
        but1['text']='  暂 停  '
        ballmove()  # 小球继续动起来
 
def ballmove(): # 小球在画布四周不停反弹
    global x, y
    while yn: # 由yn变量来控制小球是否移动
        can1.move(xq, x, y)         # 初始化,小球向右下角移动
        weizhi = can1.coords(xq)    # 获取小球的位置,一个4个元素的元组 
 
        if weizhi[0] <= 0:       # 侦测球是否超过画布左方
            x = step
        if weizhi[1] <= 0:       # 侦测球是否超过画布上方
            y = step
        if weizhi[2] >= can1width:     # 侦测球是否超过画布右方
            x = -step
        if weizhi[3] >= can1height:    # 侦测球是否超过画布下方
            y = -step
 
        can1.update()       # 刷新画布
        time.sleep(speed)   # 可以控制移动速度
 
root = Tk()
 
can1width = 200       # 定义画布宽度
can1height = 150      # 定义画布高度
step = 3        # 小于移动的步长,3个像素
x,y=3,3         # 小球移动的初始步长
speed = 0.03    # 设置移动速度
yn = False      # yn的值来控制球是否移动
 
can1 = Canvas(root, background='lightblue', width=can1width, height=can1height) # 创建画布
can1.pack()
 
but1 = Button(root, text="  动起来  ", command=ok) # 创建按钮
but1.pack()
 
xq = can1.create_oval(20, 20, 40, 40, fill='red', outline='green') # 绘制小球
 
def xq_move(event): # 鼠标拖动小球
    can1.coords(xq,event.x-10,event.y-10,event.x+10,event.y+10)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为10)
 
can1.tag_bind(xq,"<B1-Motion>",xq_move) # 小球,鼠标按住移动事件
 
root.mainloop()

  在上面的代码中,weizhi = can1.coords(xq) 会得到一个元组(x1,y1,x2,y2),x1,y1这是小球外接正方形的左上角坐标,x2,y2是右下角坐标。

  x1,即weizi[0],用于判断是否撞到画布的左边

  y1  weizi[1],用于判断是否撞到画布的上边

  x2  weizi[2],用于判断是否撞到画布的右边

  y2  weizi[3],用于判断是否撞到画布的下边

 

运行上面代码,点“动起来”可以让球移动起来,点“暂停”可以让小球静止,主要是通过yn这个布尔变量来控制。

你也可以用鼠标拖到小球到任何一个位置,因为代码中也有对鼠标按住移动事件的绑定。

运行结果:

13.PNG

 

上面是通过绘制一个小球来做为画布的对象,如果是用一个图片来做画布对象,也来做一个撞球程序,因为怎样做?

要注意:图片的坐标(x1,y1)只有2个参数,在默认情况下,是图片正中间的坐标。所以,上面的代码就要相应做出改变。

我没有找到合适的小球图片,我就以一个QQ图片(大小16X16)来代替小球。

from tkinter import *
import time
 
def ok(): # 点击按钮,由yn变量决定是暂停还是继续动起来
    global yn
    if yn == True:
        yn = False
        but1['text']='  动起来  '
    else:
        yn = True
        but1['text']='  暂 停  '
        ballmove()  # 小球继续动起来
 
def ballmove(): # 小球在画布四周不停反弹
    global x, y
    while yn: # 由yn变量业控制小球是否移动
        can1.move(xq, x, y)        # 初始化,小球向右下角移动
        weizhi = can1.coords(xq)   # 获取小球的位置,注意,是2个元素的元组
 
        if weizhi[0] -8 <= 0:          # 侦测球是否超过画布左方
            x = step
        if weizhi[1] -8 <= 0:          # 侦测球是否超过画布上方
            y = step
        if weizhi[0] + 8 >= can1width:  # 侦测球是否超过画布右方
            x = -step
        if weizhi[1] + 8 >= can1height: # 侦测球是否超过画布下方
            y = -step
 
        can1.update()       # 刷新画布
        time.sleep(speed)   # 可以控制移动速度
 
root = Tk()
 
can1width = 200       # 定义画布宽度
can1height = 150      # 定义画布高度
step = 3        # 小于移动的步长,3个像素
x,y=3,3         # 小球移动的初始步长
speed = 0.03    # 设置移动速度
yn = False      # yn的值来控制球是否移动
 
can1 = Canvas(root, background='lightblue', width=can1width, height=can1height) # 创建画布
can1.pack()
 
but1 = Button(root, text="  动起来  ", command=ok) # 创建按钮
but1.pack()
 
img1=PhotoImage(file='16-1.png')
xq=can1.create_image(20,20,image=img1) # 绘制小球图片(现暂用QQ图片代替)
 
def xq_move(event): # 鼠标拖动小球图片(现暂用QQ图片代替)
    can1.coords(xq,event.x,event.y)
 
can1.tag_bind(xq,"<B1-Motion>",xq_move) # 小球(QQ图片),鼠标按住移动事件
 
root.mainloop()

代码中:weizhi = can1.coords(xq)  是要得到的图片位置,这个位置是图片的正中间,所以要转化一下,把位置转为图片的左上角位置(x1,y1)以及右下角位置(x2,y2)。由于图片的大小是:16X16,所以应该这样转化:

x1 = weizhi[0] - 8      y1 = weizhi[1] - 8

x2 = weizhi[0] + 8      y2 = weizhi[1] + 8

 

运行结果:

14.PNG

图片的操作跟圆,线条的操作在原理上是一样的,只是要注意坐标,图片坐标为2个元素的元组,而圆,线条是4个元素的元组。

 

相撞测试:

 

如果做游戏,比如射击游戏,子弹发射出去,击中了目标,在编程角度看来,其实就是子弹这个对象跟目标这个对象相撞击了,相接触了,范围相重合了。

那如何在画布里测试2个对象的相撞测试呢? 我提供2个方法。在此之前,先把上面的1个小球在画布上反弹的代码改一下,变成2个小球不停移动,遇画布四边就反弹。

from tkinter import *
import time
 
def ok():  # 点击按钮,由yn变量决定是暂停还是继续动起来
    global yn
    if yn == True:
        yn = False
        but1['text'] = '  动起来  '
    else:
        yn = True
        but1['text'] = '  暂 停  '
        ballmove()  # 小球继续动起来
 
def ballmove():  # 小球在画布四周不停反弹
    global x1, y1, x2, y2, yn
 
    while yn:  # 由yn变量来控制小球是否移动
 
        can1.move(xq1, x1, y1)         # 初始化,小球1,红球,向右下角移动
        can1.move(xq2, x2, y2)         # 初始化,小球2,蓝球,向右上角移动
 
        wz1 = can1.coords(xq1)   # 获取小球1的位置,一个4个元素的元组
 
        if wz1[0] <= 0:          # 侦测球1是否超过画布左方
            x1 = step
        if wz1[1] <= 0:          # 侦测球1是否超过画布上方
            y1 = step
        if wz1[2] >= can1width:      # 侦测球1是否超过画布右方
            x1 = -step
        if wz1[3] >= can1height:     # 侦测球1是否超过画布下方
            y1 = -step
 
        wz2 = can1.coords(xq2)   # 获取小球2的位置,一个4个元素的元组
 
        if wz2[0] <= 0:          # 侦测球是否超过画布左方
            x2 = step2
        if wz2[1] <= 0:          # 侦测球是否超过画布上方
            y2 = step2
        if wz2[2] >= can1width:        # 侦测球是否超过画布右方
            x2 = -step2
        if wz2[3] >= can1height:       # 侦测球是否超过画布下方
            y2 = -step2
 
        can1.update()       # 刷新画布
        time.sleep(speed)   # 可以控制移动速度
 
root = Tk()
 
can1width = 200       # 定义画布宽度
can1height = 150      # 定义画布高度
step = 2        # 小球1移动的步长,2个像素
step2 = 3       # 小球2移动的步长,3个像素
x1, y1 = 2, 2         # 小球1移动的初始步长
x2, y2 = 2, -2      # 小球2移动的初始步长
speed = 0.03    # 设置移动速度
yn = False      # yn的值来控制球是否移动
r = 10            # 2个小球的半径
 
can1 = Canvas(root, background='lightblue',
              width=can1width, height=can1height)  # 创建画布
can1.pack()
 
but1 = Button(root, text="  动起来  ", command=ok)  # 创建按钮
but1.pack()
 
xq1 = can1.create_oval(20, 20, 20+2*r, 20+2*r, fill='red',
                       outline='green')  # 绘制小球1,红球,半径为r个像素
xq2 = can1.create_oval(60, 60, 60+2*r, 60+2*r, fill='blue',
                       outline='green')  # 绘制小球2,蓝球,半径为r个像素
 
def xq1_move(event):  # 鼠标拖动小球
    can1.coords(xq1, event.x-r, event.y-r, event.x+r, event.y+r)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为r)
 
def xq2_move(event):  # 鼠标拖动小球
    can1.coords(xq2, event.x-r, event.y-r, event.x+r, event.y+r)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为r)
 
can1.tag_bind(xq1, "<B1-Motion>", xq1_move)  # 小球1,鼠标按住移动事件
can1.tag_bind(xq2, "<B1-Motion>", xq2_move)  # 小球2,鼠标按住移动事件
 
root.mainloop()

上面的代码,我在前面的小球反弹代码基础上,增加了小球2(蓝球)代码,这个简单,复制粘贴一下,小球1xq1)坐标改x,yx1,y1 增加的小球xq2,坐标为x2,y2 等等,这个简单,不再多言,为了讲解简单方便,我把小球的半径10独立出来,多设置一个变量r,半径初始化为10

运行上面的代码,点击“动起来”2个小于就自由地在画布上不停移动,反弹了。

15.PNG

 

但上面代码还没有加入相撞测试代码,下面我先用第1种方法来判断2球相撞,这个方法是:find_overlapping()

语法:find_overlapping(x1, y1, x2, y2)  # 4个坐标参数组成一个矩形范围

返回值是在这个限定矩形内的画布对象的 ID组成的元组,画面对象部分或全部在这个限定矩形都算。

比如: IDS=can1.find_overlapping(wz1[0], wz1[1], wz1[2], wz1[3])

上面4个参数分别是小球1,即红球的外接方形坐标,当小球1和小球2还没有相撞时,IDS这个返回值是 (1,) 即在这个限定范围内只有ID1的小球1,当相撞时,返回值 IDS=(1,2)  len(IDS)=2 这说明限定矩形内有2个画面对象,其实,只要 len(IDS)>=2 就说明有2个及以上的画布对象相撞了。

好了,我把加入的相撞判断代码加入,大家测试一下,相撞判断代码加入到小球移动循环中,即小球每移动一步,就判断一下。

from tkinter import *
import time
 
def ok():  # 点击按钮,由yn变量决定是暂停还是继续动起来
    global yn
    if yn == True:
        yn = False
        but1['text'] = '  动起来  '
    else:
        yn = True
        but1['text'] = '  暂 停  '
        ballmove()  # 小球继续动起来
 
def ballmove():  # 小球在画布四周不停反弹
    global x1, y1, x2, y2, yn
 
    while yn:  # 由yn变量来控制小球是否移动
 
        can1.move(xq1, x1, y1)         # 初始化,小球1,红球,向右下角移动
        can1.move(xq2, x2, y2)         # 初始化,小球2,蓝球,向右上角移动
 
        wz1 = can1.coords(xq1)   # 获取小球1的位置,一个4个元素的元组
 
        if wz1[0] <= 0:          # 侦测球1是否超过画布左方
            x1 = step
        if wz1[1] <= 0:          # 侦测球1是否超过画布上方
            y1 = step
        if wz1[2] >= can1width:        # 侦测球1是否超过画布右方
            x1 = -step
        if wz1[3] >= can1height:       # 侦测球1是否超过画布下方
            y1 = -step
 
        wz2 = can1.coords(xq2)   # 获取小球2的位置,一个4个元素的元组
 
        if wz2[0] <= 0:          # 侦测球是否超过画布左方
            x2 = step2
        if wz2[1] <= 0:          # 侦测球是否超过画布上方
            y2 = step2
        if wz2[2] >= can1width:        # 侦测球是否超过画布右方
            x2 = -step2
        if wz2[3] >= can1height:       # 侦测球是否超过画布下方
            y2 = -step2
 
        can1.update()       # 刷新画布
        time.sleep(speed)   # 可以控制移动速度
 
        # 相撞判断:
        IDS=can1.find_overlapping(wz1[0], wz1[1], wz1[2], wz1[3]) # 小球1的位置坐标参数
        # find_overlapping(外接四方形左上角坐标,外接四方形右下角坐标),返回的坐标范围内的画面对象元组。
        if len(IDS) >= 2: # 在同一个四方框时有2个以上画布对象,即为相撞
            print('相撞了')
            yn = False
            but1['text']='  动起来  '
            break       # 退出循环,让小球暂停移动
 
root = Tk()
 
can1width = 200       # 定义画布宽度
can1height = 150      # 定义画布高度
step = 2        # 小球1移动的步长,2个像素
step2 = 3       # 小球2移动的步长,3个像素
x1, y1 = 2, 2         # 小球1移动的初始步长
x2, y2 = 2, -2      # 小球2移动的初始步长
speed = 0.03    # 设置移动速度
yn = False      # yn的值来控制球是否移动
r = 10            # 2个小球的半径
# xz=False # 相撞的状态
 
can1 = Canvas(root, background='lightblue',
              width=can1width, height=can1height)  # 创建画布
can1.pack()
 
but1 = Button(root, text="  动起来  ", command=ok)  # 创建按钮
but1.pack()
 
xq1 = can1.create_oval(20, 20, 20+2*r, 20+2*r, fill='red',
                       outline='green')  # 绘制小球1,红球,半径为r个像素
xq2 = can1.create_oval(60, 60, 60+2*r, 60+2*r, fill='blue',
                       outline='green')  # 绘制小球2,蓝球,半径为r个像素
 
def xq1_move(event):  # 鼠标拖动小球
    can1.coords(xq1, event.x-r, event.y-r, event.x+r, event.y+r)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为r)
 
def xq2_move(event):  # 鼠标拖动小球
    can1.coords(xq2, event.x-r, event.y-r, event.x+r, event.y+r)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为r)
 
can1.tag_bind(xq1, "<B1-Motion>", xq1_move)  # 小球1,鼠标按住移动事件
can1.tag_bind(xq2, "<B1-Motion>", xq2_move)  # 小球2,鼠标按住移动事件
 
root.mainloop()

 

运行后,第一次相撞的位置: 丶丌皛

16.PNG

经过测试,小球相撞,都可以成功判断出。

这种方法判断画布对象相撞,代码也比较容易写,只要把相撞的一个对象的坐标加入find_overlapping()方法中,只要返回值的元组有2个及以上元素,就说明有2个或2个以上的对象相撞了。

但这种方法,有时,(如上图所示)当小球相撞了,即小球的外接方框相撞了,但看上去,小球还没有撞到,小球越大,这个情况越明显。但小球半径小的时候,这个情况大大好转,你可以把上面的代码的r=10 改成 r=3  再测试一下,要加快小球的运行,把 speed=0.03 改成 speed=0.01

我看用这种方法来判断子弹射中目标比较好,因为子弹相对于目标都是很小的对象。

 

下面我来讲第2种方法来判断小球相撞,我们知道,当2个小球相撞时,刚一接触,这2个小球是相切的,这2个小球的球心之间的距离正好是2个小球半径相加,目前这2个球半径是相同的,所以,判断2个小球相撞的条件,就是2球心之间的距离<=2*r  当大于2*r距离,都说明2球的距离还没有接触。

下面的代码,我把第2种判断小球相撞的代码取代第1种方法:

from tkinter import *
import time
 
def ok():  # 点击按钮,由yn变量决定是暂停还是继续动起来
    global yn
    if yn == True:
        yn = False
        but1['text'] = '  动起来  '
    else:
        yn = True
        but1['text'] = '  暂 停  '
        ballmove()  # 小球继续动起来
 
def ballmove():  # 小球在画布四周不停反弹
    global x1, y1, x2, y2, yn
 
    while yn:  # 由yn变量来控制小球是否移动
 
        can1.move(xq1, x1, y1)         # 初始化,小球1,红球,向右下角移动
        can1.move(xq2, x2, y2)         # 初始化,小球2,蓝球,向右上角移动
 
        wz1 = can1.coords(xq1)   # 获取小球1的位置,一个4个元素的元组
 
        if wz1[0] <= 0:          # 侦测球1是否超过画布左方
            x1 = step
        if wz1[1] <= 0:          # 侦测球1是否超过画布上方
            y1 = step
        if wz1[2] >= can1width:        # 侦测球1是否超过画布右方
            x1 = -step
        if wz1[3] >= can1height:       # 侦测球1是否超过画布下方
            y1 = -step
 
        wz2 = can1.coords(xq2)   # 获取小球2的位置,一个4个元素的元组
 
        if wz2[0] <= 0:          # 侦测球是否超过画布左方
            x2 = step2
        if wz2[1] <= 0:          # 侦测球是否超过画布上方
            y2 = step2
        if wz2[2] >= can1width:        # 侦测球是否超过画布右方
            x2 = -step2
        if wz2[3] >= can1height:       # 侦测球是否超过画布下方
            y2 = -step2
 
        can1.update()       # 刷新画布
        time.sleep(speed)   # 可以控制移动速度
 
        # 相撞判断:
        SS=pow( (((wz2[0]+10)-(wz1[0]+10))**2 + ((wz2[1]+10)-(wz1[1]+10))**2),1/2 ) # 小球每移动一次,即时求出2球的圆心之间的间距
 
        print('ss=',SS) # 输出2球心之间的间距让大家看看
 
        if SS<=20 :  # 20就是2个球的间距,等于或小于2球心之间的间距,就说明2球相撞了。
            xz=True
            print('相撞了')
            yn = False
            but1['text']='  动起来  '
            break
 
root = Tk() # 源码来自wb98.com
 
can1width = 200       # 定义画布宽度
can1height = 150      # 定义画布高度
step = 2        # 小球1移动的步长,2个像素
step2 = 3       # 小球2移动的步长,3个像素
x1, y1 = 2, 2         # 小球1移动的初始步长
x2, y2 = 2, -2      # 小球2移动的初始步长
speed = 0.03    # 设置移动速度
yn = False      # yn的值来控制球是否移动
r = 10            # 2个小球的半径
# xz=False # 相撞的状态
 
can1 = Canvas(root, background='lightblue',
              width=can1width, height=can1height)  # 创建画布
can1.pack()
 
but1 = Button(root, text="  动起来  ", command=ok)  # 创建按钮
but1.pack()
 
xq1 = can1.create_oval(20, 20, 20+2*r, 20+2*r, fill='red',
                       outline='green')  # 绘制小球1,红球,半径为r个像素
xq2 = can1.create_oval(60, 60, 60+2*r, 60+2*r, fill='blue',
                       outline='green')  # 绘制小球2,蓝球,半径为r个像素
 
def xq1_move(event):  # 鼠标拖动小球
    can1.coords(xq1, event.x-r, event.y-r, event.x+r, event.y+r)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为r)
 
def xq2_move(event):  # 鼠标拖动小球
    can1.coords(xq2, event.x-r, event.y-r, event.x+r, event.y+r)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为r)
 
can1.tag_bind(xq1, "<B1-Motion>", xq1_move)  # 小球1,鼠标按住移动事件
can1.tag_bind(xq2, "<B1-Motion>", xq2_move)  # 小球2,鼠标按住移动事件
 
root.mainloop()

运行后,第1次相撞:

17.PNG

 

经测试,这种判断2球相撞(相切)效果不错,无论球的半径大小都还不错,但是要不断计算2球心之间的距离SS

SS=pow( (((wz2[0]+r)-(wz1[0]+r))**2 + ((wz2[1]+r)-(wz1[1]+r))**2),1/2 )
# 小球每移动一次,即时求出2球的圆心之间的间距

说明一下:pow() 计算平方根:

pow(A,1/2)  # 计算A2次方根

pow(A,1/3)  # 计算A3次方根

 

2球心之间的距离是一个数字问题,我稍讲解一下:

18.PNG

 

如上图所示:

小球1的外接正方方形坐标为 wz1[0], wz1[1]

小球2的外接正方方形坐标为 wz2[0], wz2[1]

 

小球1的球心坐标为 wz1[0] + r , wz1[1] + r

小球2的球心坐标为 wz2[0] + r , wz2[1] + r

 

B = 2的横坐标 - 1的横坐标 =  (wz2[0] + r) - (wz1[0] + r)

A = 2的纵坐标 - 1的纵坐标 =  (wz2[1] + r) - (wz1[1] + r)

 

C的平方=A的平方 + B的平方

C**2 = A**2 + B**2

 

2球心的距离为 C = 2次方根 [ A2次方 + B2次方)]

            C= pow( (A**2 + B**2), 1/2 )

 

2球心之间的距离这个数字问题,我就讲解到这,不明白的,多看几遍吧。

 

 

上面的代码,我想再改进一下,因为2个球相撞后,再点“动起来”按钮好几次,2球还是处于相撞状态,我希望相撞后,在2球分开之前,只算1次相撞的情况。

我新加入一个相撞的布尔变量:xz ,当相撞前,球心之间的距离只要>2r ,xz=False 当相撞后,xz=True,在暂停的条件加上 xz=True 这样,相撞后到分开前,只执行一次暂停小球移动。

改动的代码,如下,不解释了,对我写的文章有兴趣,可以关注我,收藏我的文章。我的网站 wb98.com

from tkinter import *
import time
 
def ok():  # 点击按钮,由yn变量决定是暂停还是继续动起来
    global yn
    if yn == True:
        yn = False
        but1['text'] = '  动起来  '
    else:
        yn = True
        but1['text'] = '  暂 停  '
        ballmove()  # 小球继续动起来
 
def ballmove():  # 小球在画布四周不停反弹
    global x1, y1, x2, y2, yn, xz
 
    while yn:  # 由yn变量来控制小球是否移动
 
        can1.move(xq1, x1, y1)         # 初始化,小球1,红球,向右下角移动
        can1.move(xq2, x2, y2)         # 初始化,小球2,蓝球,向右上角移动
 
        wz1 = can1.coords(xq1)   # 获取小球1的位置,一个4个元素的元组
 
        if wz1[0] <= 0:          # 侦测球1是否超过画布左方
            x1 = step
        if wz1[1] <= 0:          # 侦测球1是否超过画布上方
            y1 = step
        if wz1[2] >= can1width:        # 侦测球1是否超过画布右方
            x1 = -step
        if wz1[3] >= can1height:       # 侦测球1是否超过画布下方
            y1 = -step
 
        wz2 = can1.coords(xq2)   # 获取小球2的位置,一个4个元素的元组
 
        if wz2[0] <= 0:          # 侦测球是否超过画布左方
            x2 = step2
        if wz2[1] <= 0:          # 侦测球是否超过画布上方
            y2 = step2
        if wz2[2] >= can1width:        # 侦测球是否超过画布右方
            x2 = -step2
        if wz2[3] >= can1height:       # 侦测球是否超过画布下方
            y2 = -step2
 
        can1.update()       # 刷新画布
        time.sleep(speed)   # 可以控制移动速度
 
        # 相撞判断:
        # 小球每移动一次,即时求出2球的圆心之间的间距
        SS = pow((((wz2[0]+r)-(wz1[0]+r))**2 +
                 ((wz2[1]+r)-(wz1[1]+r))**2), 1/2)
 
        print('ss=', SS)  # 输出2球心之间的间距让大家看看
 
        if SS > 2*r:
            xz = False  # 相距大于20个像素,肯定相撞状态为假
 
        if SS <= 20 and xz == False:  # 20就是2个球的间距,等于或小于2球心之间的间距,就说明2球相撞了。
            xz = True
            print('相撞了')
            yn = False
            but1['text'] = '  动起来  '
            break
 
root = Tk() # 源码来自wb86.com
 
can1width = 200       # 定义画布宽度
can1height = 150      # 定义画布高度
step = 2        # 小球1移动的步长,2个像素
step2 = 3       # 小球2移动的步长,3个像素
x1, y1 = 2, 2         # 小球1移动的初始步长
x2, y2 = 2, -2      # 小球2移动的初始步长
speed = 0.03    # 设置移动速度
yn = False      # yn的值来控制球是否移动
r = 10            # 2个小球的半径
xz = False  # 相撞的状态
 
can1 = Canvas(root, background='lightblue',
              width=can1width, height=can1height)  # 创建画布
can1.pack()
 
but1 = Button(root, text="  动起来  ", command=ok)  # 创建按钮
but1.pack()
 
xq1 = can1.create_oval(20, 20, 20+2*r, 20+2*r, fill='red',
                       outline='green')  # 绘制小球1,红球,半径为r个像素
xq2 = can1.create_oval(60, 60, 60+2*r, 60+2*r, fill='blue',
                       outline='green')  # 绘制小球2,蓝球,半径为r个像素
 
def xq1_move(event):  # 鼠标拖动小球
    can1.coords(xq1, event.x-r, event.y-r, event.x+r, event.y+r)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为r)
 
def xq2_move(event):  # 鼠标拖动小球
    can1.coords(xq2, event.x-r, event.y-r, event.x+r, event.y+r)
    # 将鼠标当前位置转为小球的外接正方形的左上角和右下角坐标(小球的半径为r)
 
can1.tag_bind(xq1, "<B1-Motion>", xq1_move)  # 小球1,鼠标按住移动事件
can1.tag_bind(xq2, "<B1-Motion>", xq2_move)  # 小球2,鼠标按住移动事件
 
root.mainloop()


Scavas组件要学的知识还有很多,本人也只是学了一点丁基础知识,以后学了很多的Scavas知识,再来讲解Scavas组件更深入的知识吧。

此文章来自:wb98.com  网站还有相关的系列课程文章,感兴趣的可以前往。

 

 

下一篇文章讲解一下导入模块的方法,以及不同导入模块方法有什么区别,如 import tkinter.ttk form tkinter.ttk import * 有什么不同。


来源:济亨网

本文链接:http://wb98.com/post/333.html

    << 上一篇 下一篇 >>

    湘公网安备 43011102000514号 - 湘ICP备08100508号