"""本项目暂停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())