tkinter和pymunk的Sprite类练习参考代码

"""本项目暂停2019/4/17,项目的初衷是编制一个受物理引擎pymunk的Sprite类,由于种种原因,本项目可能不会再重启,代码供人参考——李兴球"""

import time
import math
import tkinter

class Vec2D(tuple):
    """2D向量类,继承于元组,提供以下的向量操作,a,b是向量,k是数字

       a+b 向量加法
       a-b 向量减法
       a*b 向量内积
       k*a and a*k 缩放向量
       |a| 向量的绝对值
       a.rotate(angle) 旋转向量
    """
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    def __add__(self, other):
        return Vec2D(self[0]+other[0], self[1]+other[1])
    def __mul__(self, other):
        if isinstance(other, Vec2D):
            return self[0]*other[0]+self[1]*other[1]
        return Vec2D(self[0]*other, self[1]*other)
    def __rmul__(self, other):
        if isinstance(other, int) or isinstance(other, float):
            return Vec2D(self[0]*other, self[1]*other)
    def __sub__(self, other):
        return Vec2D(self[0]-other[0], self[1]-other[1])
    def __neg__(self):
        return Vec2D(-self[0], -self[1])
    def __abs__(self):
        return (self[0]**2 + self[1]**2)**0.5
    def rotate(self, angle):
        """旋转向量
        """
        perp = Vec2D(-self[1], self[0])
        angle = angle * math.pi / 180.0        
        c, s = math.cos(angle), math.sin(angle)
        return Vec2D(self[0]*c+perp[0]*s, self[1]*c+perp[1]*s)
    def __getnewargs__(self):
        return (self[0], self[1])
    def __repr__(self):
        return "(%.2f,%.2f)" % self
    
  
def negative_y_cors(points):
    """把每个点的y坐标取反"""
    amounts = len(points)
    points = [ (2*(1-(index%2)) -1)  * points[index] for index in range(amounts) ]
    return points

class _Window(tkinter.Tk):
    
    _screen = None
    def __init__(self,width,height,title="窗口标题",bgcolor='white',background=None):
        tkinter.Tk.__init__(self)
        
        self.__bgimages = {}         # 背景字典
        self.raw_title = title       # 仅记录原始标题
        self.resizable(False,False)  # noresize不可变窗口大小
        self.title(title)
        self.width = width
        self.height = height
        self.canvas = tkinter.Canvas(self,width=self.width,height=self.height,bg=bgcolor,border=0 )
        self.canvas.config(scrollregion=(-width//2, -height//2, width//2, height//2 ))
        self.canvas.pack()
        self.__blankimage = tkinter.PhotoImage(width=1, height=1) # 新建空白图形
         
        self.__background = self.canvas.create_image(0,0,image=self.__blankimage) # 这是背景item,是个id号

        self.set_background(background)

    def set_background(self,image):         
        
        if image:                   # 如果image有数据并且字典中有,则直接返回,否则添加到背景字典里。
            self.__background_image = self.__bgimages.setdefault(image,tkinter.PhotoImage(file=image))
        else:
            self.__background_image = self.__blankimage
        self.canvas.itemconfig(self.__background,image=self.__background_image)
        
    def set_bgcolor(self,color):
        
        self.canvas.config(bg=color)

    def set_width_and_height(self,width,height):
        
        """设置窗口画布的宽度和高度,别名是size"""
        
        self.canvas.config(width=width,height=height)
        self.canvas.config(scrollregion=(-width//2, -height//2, width//2, height//2 ))
        
    def ontimer(self,fun,millisecond=0):
        """定时器,在一定时间后再次调用fun,时间单位为毫秒"""
        if millisecond == 0:
            self.canvas.after_idle(fun)
        else:
            self.canvas.after(millisecond, fun)
            
    # 定义别名
    size = set_width_and_height
    background = set_background
    bgcolor = set_bgcolor

class Sprite():
    def __init__(self):
        if _Window._screen is None:            
            _Window._screen = create_window(480,360)            
        self.canvas = _Window._screen.canvas
        self.dx = 0           # 代表水平速度
        self.dy = 0           # 代表垂直速度
        self.angle = 0        # 角度
        self.itemid = 0       # 角色id号,具体在子类中的canvas分配
        
    def move(self):
        
        self.canvas.move(self.itemid,self.dx,-self.dy)
        self.canvas.update_idletasks()
        
    def bbox(self):
        
        x1,y1,x2,y2 =  self.canvas.bbox(self.itemid)
        return x1,-y1,x2,-y2
    
    
    def coords(self,position=None):
        """返回形状的两点坐标或设置坐标"""
        if position == None:
           points =  self.canvas.coords(self.itemid)
           return negative_y_cors(points)
        else:    
           self.canvas.coords(self.itemid,negative_y_cors(position))
           
    def show(self):
        """显示出来"""
        self.canvas.itemconfig(self.itemid,state='normal')
        self.canvas.update_idletasks()
        
    def hide(self):
        """隐藏"""
        self.canvas.itemconfig(self.itemid,state='hidden')
        self.canvas.update_idletasks()

    def point_list(self):
        """返回得到的每个顶点列表,旋转形状需要获取形状的每个顶点坐标。"""
        
    def get_center(self):
        points = self.coords()
        amounts = len(points)
        all_x = [points[i] for i in range(0,amounts,2)]
        all_y = [points[i] for i in range(1,amounts,2)]
        self._left = min(all_x)
        self._right = max(all_x)
        self._top = max(all_y)
        self._bottom = min(all_y)
        self._centerx = (self._left + self._right)/2.0
        self._centery = (self._bottom + self._top)/2.0
        return points
        
    def rotate(self,angle):
        
        points = self.get_center()   # 获取多边形的每一个顶点和得到中心点坐标
        print("点们:",points)       # 获取到的是这样的列表: [-50.0, 25.0, 50.0, -25.0]
        amounts = len(points)
        points = [ (points[i],points[i+1]) for i in range(0,amounts,2) ]
        x = self._centerx
        y = self._centery
        print("中心点:",x,y)
        points = [ Vec2D(point[0]-x,point[1]-y) for point in points]           # 转变成向量
        points = [ point.rotate(angle) for point in points]    # 旋转
        points = [ (point[0]+x,point[1]+y) for point in points]    # 转换回元组
        points_y = [ negative_y_cors(point) for point in points] # 把每个点的y坐标取负

##        if self.canvas.type(self.itemid) in ["arc","oval","rectangle"]:
##            self.canvas.coords(self.itemid, *points)
##             
##        elif self.canvas.type(self.itemid) in ["polygon"]:
        p = []
        for point in points_y:
            p.append(point[0])
            p.append(point[1])
        print(p)
        self.canvas.coords(self.itemid,p)
             
        self.angle = angle
        self.canvas.update_idletasks()
        
    def setheading(self,delta_angle):
        
        self.angle = self.angle + delta_angle
        self.angle = self.angle % 360
        print("角度=",self.angle)
        self.rotate(self.angle)
        
        
           
class Line(Sprite):
    
    def __init__(self,pointlist,color="blue",width=1,capstyle='round',joinstyle='round'):
        """画直线,pointlist:坐标点列表,color:颜色,width:笔迹宽度,
           capstyle:末端风格,值为'round'或'butt'或'projecting'
           joinstyle:连接风格,值为'round'、'bevel'、'miter' 。
       """
        Sprite.__init__(self)
        pointlist = negative_y_cors(pointlist)
        self.itemid = self.canvas.create_line(pointlist,fill=color,width=width,capstyle=capstyle,joinstyle=joinstyle)
    
    
class Polygon(Sprite):    
    def __init__(self,pointlist,color="blue"):
        Sprite.__init__(self)
        pointlist = negative_y_cors(pointlist)
        self.itemid = self.canvas.create_polygon(pointlist,fill=color,outline=color,width=0)

        
class Arc(Sprite):
    def __init__(self,bbox,color="blue",start=0.0,end=90.0,style='pieslice'):
        """画扇形,弦形,和弧形,bbox:绑定盒,color:颜色,start:起始角度,end:结束角度,style:样式(pieslice、chord、arc)
        tkinter.PIESLICE -> 'pieslice'扇, tkinter.CHORD ->'chord'弦, tkinter.ARC ->'arc'弧
        """
        Sprite.__init__(self)
        bbox = negative_y_cors(bbox)
        self.itemid = self.canvas.create_arc(bbox ,fill=color,outline=color,width=0,start=start,extent=end,style=style)
        
class Rectangle(Sprite):
    def __init__(self,bbox,color="blue"):
        Sprite.__init__(self)
        bbox = negative_y_cors(bbox)
        self.itemid = self.canvas.create_rectangle(bbox ,fill=color,outline=color,width=0)
        
class Circle(Sprite):
    def __init__(self,bbox,color="blue"):
        Sprite.__init__(self)
        bbox = negative_y_cors(bbox)
        self.itemid = self.canvas.create_oval(bbox,fill=color,outline=color)

class Text(Sprite):
    def __init__(self,position,string,color="blue",font=("黑体",24,"normal")):
        Sprite.__init__(self) 
        self.itemid = self.canvas.create_text(position,text=string,fill=color,font=font)

        
  
def create_window(width,height,title="窗口标题",bgcolor='white',background=None):
     
    if _Window._screen is None:
         _Window._screen = _Window(width,height,title=title,bgcolor=bgcolor,background=background)
          
    return _Window._screen
     
          
if __name__ == "__main__":

    
    def display_cursor(event):
        """在标题栏里显示标题"""    
        x = window.canvas.canvasx(event.x)
        y = -window.canvas.canvasy(event.y)
        window.title("(" + str(x) + "," + str(y) + ")_" + window.raw_title)
        
    
    背景表 = ["images/" + str(i) + ".png"  for i in range(40)]
    amounts = len(背景表)
    
    window = create_window(480,360,title="窗口标题",bgcolor='cyan',background='background.png')
    
    window.canvas.bind("<Motion>",display_cursor)
    window.size(800,600)
    index = 0
    def alt_background():
         global index 
         window.background(背景表[index])
         index = index + 1
         index = index % amounts
         window.canvas.update_idletasks()
         window.ontimer(alt_background,10)
         
    alt_background()
    window.canvas.bind("<Button-1>",lambda event:window.destroy())
 
 


   
 
        
         
     
 

 

关于李兴球

李兴球的博客是Python创意编程原创博客
此条目发表在pymunk, python, tkinter分类目录。将固定链接加入收藏夹。