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