HOME Python download 書き込む

11. Canvas を使って納涼しよう


1. 初めに

前回は、ドラッグすると動くオブジェクトを作りましたが、今回は自動的に動くオブジェクト(つまり、動画) を Tk.Canvas に描いてみます。 お題は蛍と花火です。この文章を書いているのは7月なので、季節柄を考えて選びました。

2. 蛍

生成する Window を図1に示します。黄色い円が、紺地の中をふらふら動くだけの簡単なプログラムです。


図1:hotaru.py で生成する Window

class Hotaru を定義し、縦横の速度と加速度およびキャンバス上の ID 番号を内部変数に持ちます。 加速度はランダムに変化しますが、進行方向に対してブレーキがかかるようにしています。 そうしないと蛍がすぐいなくなってしまいます。ブレーキがかかると蛍がふらふらと漂います。

ソースコードを [code 1] に示します。簡単なプログラムですので、コードも短くてすみます。

[code 1] (hotaru.py)

01:     #! /usr/bin/env python
02:     # -*- coding: shift_jis -*-
03:     """
04:     hotaru.py
05:     
06:     July 08, 2005
07:     """
08:     import Tkinter as Tk
09:     import random as R
10:     
11:     HOTARU_SIZE = 3
12:     HOTARU_COLOR = 'yellow'
13:     NIGHT_COLOR = 'midnightblue'
14:     
15:     class Hotaru:
16:         canvas = None
17:     
18:         def __init__(self, x, y):
19:             self.id = Hotaru.canvas.create_oval(x-HOTARU_SIZE, y-HOTARU_SIZE, x+HOTARU_SIZE, y+HOTARU_SIZE,
20:                                                 fill=HOTARU_COLOR, width=0)
21:             self.vx=0
22:             self.vy=0
23:             self.ax = R.randint(-10,10)
24:             self.ay = R.randint(-10,10)
25:             
26:     
27:     
28:         def moving(self):
29:             vx1=self.ax+self.vx
30:             vy1=self.ay+self.vy
31:             dx = (self.vx+vx1)/2
32:             dy = (self.vy+vy1)/2
33:             Hotaru.canvas.move(self.id, dx, dy)
34:             self.vx=vx1
35:             self.vy=vy1
36:             self.ax = R.randint(-10,10) - self.vx/2
37:             self.ay = R.randint(-10,10) - self.vy/2
38:     
39:            
40:     class Frame (Tk.Frame):       
41:         
42:         def __init__(self, master=None):
43:             Tk.Frame.__init__(self, master)
44:             self.master.title("Hotaru")
45:             self.master.geometry("+20+20")
46:             self.cvs = Tk.Canvas(self, width=500, height=500, relief=Tk.SUNKEN, borderwidth=2, bg=NIGHT_COLOR)
47:             self.cvs.pack(fill=Tk.BOTH, expand=1)
48:     
49:             # assign a Hotaru class parameter
50:             Hotaru.canvas=self.cvs
51:     
52:             self.counter=0
53:             self.hotaru_list = []
54:             self.create_hotaru(30)
55:             self.move_hotaru()
56:     
57:         def move_hotaru(self):
58:             for p in self.hotaru_list:
59:                 p.moving()
60:             self.counter += 1
61:             self.counter%10 or self.create_hotaru(1)  # make new hotal every one second
62:             self.cvs.after(100, self.move_hotaru)
63:     
64:     
65:         def create_hotaru(self, n):
66:             for i in range(n):
67:                 x=R.randint(50, 450)
68:                 y=R.randint(50, 450)
69:                 self.hotaru_list.append(Hotaru(x,y))
70:             
71:     
72:     ##-----------------------------------------------------
73:     if __name__ == '__main__':
74:         f = Frame()
75:         f.pack(fill=Tk.BOTH, expand=1)
76:         f.mainloop()

2.1. 簡単な説明

カッコ内は [code 1] の行番号です。

3. 花火

hotaru.py は全てを Random に押し付けた、あまりに安易なプログラムなので、少しは本格的?になるように 花火のプログラムを作りました。実はこのプログラムも 140 行ほどの短いプログラムです。 生成する Window を図2に示します。短いプログラムですが意外と臨場感があります。


図2: hanabi.py で生成する Window

hotaru.py と同様に、Hanabi というオブジェクトを作り、位置、初速度、寿命、色を指定します。 花火は初速度と重力加速度に従って動き、寿命が来ると消えます。 また、花火のサブクラス Mother を作り、打ち上げる弾を表します。 ソースコードを [code 2]に示します。

[code 2] (hanabi.py)

 01:     #! /usr/bin/env python
 02:     # -*- coding: shift_jis -*-
 03:     """
 04:     hanabi.py
 05:     
 06:     July 08, 2005
 07:     """
 08:     import Tkinter as Tk
 09:     import random as R
 10:     import math as M
 11:     
 12:     
 13:     
 14:     HANABI_COLORS = ['red', 'lightcyan',  '#FF33FF', '#FF99FF', '#FFFFCC', '#FFCC00', 
 15:                      'magenta', 'blueviolet', 'white', '#FF99FF']
 16:     NIGHT_COLOR = '#000033'
 17:     G = 1
 18:     HR = 1
 19:     NHANA = 20
 20:     N_COLORS = len(HANABI_COLORS)-1
 21:     
 22:     
 23:     def rand_angle(n):
 24:         """ +- x degree, where x is degree of radius of children """
 25:         return M.pi * R.randint(-10,10)/(float(n)*10.0)
 26:     
 27:     class Hanabi:
 28:         canvas = None
 29:         list = None
 30:     
 31:     
 32:         def __init__(self, **p):
 33:             x = p['x']
 34:             y = p['y']
 35:             self.vx = p['vx']
 36:             self.vy = p['vy']
 37:             self.lifetime = p['lifetime']
 38:             self.id = Hanabi.canvas.create_oval(x-HR, y-HR, x+HR, y+HR, fill=p['color'], width=0)
 39:             self.counter=0
 40:     
 41:     
 42:         def moving(self):
 43:             if(self.counter<self.lifetime):
 44:                 Hanabi.canvas.move(self.id, self.vx, self.vy)
 45:                 self.counter += 1
 46:                 self.vy += G
 47:             else:
 48:                 self.disappear()
 49:     
 50:     
 51:         def disappear(self):
 52:             Hanabi.canvas.delete(self.id)
 53:             self.id = None
 54:     
 55:     
 56:     
 57:     class Mother(Hanabi):
 58:         
 59:         def disappear(self):
 60:             r0 = R.randint(13,17)
 61:             v0 = R.randint(18, 22)
 62:             nc0 = R.randint(0,N_COLORS)
 63:             nc1 = R.randint(1, N_COLORS-1)
 64:             x1,y1,x2,y2 = Hanabi.canvas.coords(self.id)
 65:             x, y = (x1+x2)/2, (y1+y2)/2
 66:             self.make_sphere(NHANA, x, y, r0, v0, HANABI_COLORS[nc0], self.vx, self.vy)
 67:             self.make_sphere(NHANA/2, x, y, r0/3, v0/3, HANABI_COLORS[(nc0+nc1)%(N_COLORS+1)], self.vx, self.vy)
 68:             Hanabi.disappear(self)
 69:     
 70:     
 71:         def make_sphere(self, nh, x, y, r, v, c, vx0, vy0):
 72:             d_eta = 2.0 * M.pi / float(NHANA)
 73:             eta = rand_angle(NHANA) 
 74:             for i in range(NHANA/2):
 75:                 dxy = M.sin(eta)
 76:                 n = M.floor(dxy * nh)
 77:                 d_theta = n and 2 * M.pi/n or 2* M.pi
 78:                 theta = rand_angle(NHANA)
 79:                 
 80:                 if i%2:
 81:                     theta += d_theta*0.5
 82:                     
 83:                 for j in range(int(n)):
 84:                     dx = M.cos(theta) * dxy 
 85:                     dy = M.sin(theta) * dxy
 86:                     Hanabi.list.append(Hanabi(x=r*dx+x, y=r*dy+y, color=c, 
 87:                                        vx=v*dx+vx0, vy=v*dy+vy0,  lifetime=R.randint(8,15)))
 88:                     theta += d_theta
 89:                 eta +=d_eta
 90:     
 91:     
 92:     
 93:     
 94:     class Frame (Tk.Frame):       
 95:         
 96:         def __init__(self, master=None):
 97:             Tk.Frame.__init__(self, master)
 98:             self.master.title("Hanabi")
 99:             self.master.geometry("+20+20")
100:             Hanabi.canvas = Tk.Canvas(self, width=500, height=700,
101:                                        relief=Tk.SUNKEN, borderwidth=2, bg=NIGHT_COLOR)
102:                                        
103:             Hanabi.canvas.pack(fill=Tk.BOTH, expand=1)
104:     
105:             Hanabi.list = []
106:             self.rep()
107:     
108:         def rep(self):
109:             # delete disappeared items
110:             n = len(Hanabi.list)
111:             i=0
112:             while(i<n):
113:                 if(Hanabi.list[i].id):
114:                     i += 1
115:                 else:
116:                     del Hanabi.list[i]
117:                     n -= 1
118:     
119:             # move items
120:             for p in Hanabi.list:
121:                 p.moving()
122:     
123:             # launch another hanabi, one every second in average
124:             if R.randint(0,100)>90:
125:                 self.create_hanabi()
126:     
127:             # repeat
128:             self.after(100, self.rep)
129:     
130:     
131:         def create_hanabi(self):
132:             vy0 = R.randint(-33,-31)
133:             Hanabi.list.append(Mother(x=R.randint(150, 350), y=695, vx=R.randint(-1,1), vy=vy0,
134:                                       color='#CC6600', lifetime=vy0*(-1)-R.randint(3,5)))
135:             
136:     
137:     ##-----------------------------------------------------
138:     if __name__ == '__main__':
139:         f = Frame()
140:         f.pack(fill=Tk.BOTH, expand=1)
141:         f.mainloop()

3.1. インポートするモジュールとグローバル変数

3.2. 関数

3.3. class Hanabi

花火を表すクラスです。クラス変数に次の2つがあります。(27--53)

初期化するとき (32--39) は、キーワードパラメターをとります。キーワードは以下の通りです。

内部変数として以下のものを保存します。

Hanabi には moving (39--45) と disappear (51--53) の2つのメソッドがあります。

3.4. class Mother

Hanabi のサブクラスです。(57--82)
打ち上げる花火のクラスで、寿命が来ると Hanabi を多数生成して消えます。

__init__, movingHanabi と同じです。
Mother は消えるときたくさんの子を作るので、disappearHanabi と異なります。 self.make_sphere を2回使って2重の輪を持つ花火を作ります。内側と外側の花火の色は違う色にします。

make_sphere(self, nh, x, y, r, v, c, vx0, vy0) は親玉が破裂したとき、小玉が球面上に飛び散るようにする メソッドです。(70--84)
以下の説明は図を用いていないのでちょっとわかりにくいのですが、要するに、 典型的な花火である
割物(わりもの) をシミュレートしています。

3.5. class Frame

メインクラスです。(94--134)

4. 終わりに

いかがでしたでしょうか? 今回は、Tk.Canvas を使うと簡単に動画が描けることを示しました。 皆様もいろいろと楽しい動画を作ってみてください。

今回で Tk.Canvas の解説はおしまいです。 次回は Tk.Text について説明します。