動かす(アニメーション)
少し動かして,少し待つ.それを繰り返すだけで絵が動き出します.
第 8 章でかめを何匹も作れるようになりました. けれども,どのかめも一瞬で動き終わってしまいます. プログラムが最後の行まで走り切ると,そこで絵は止まったままです.
今回は,絵を動かし続ける方法を覚えます.
やることは驚くほど単純で,「少し動かす → 少し待つ」を
何度も何度も繰り返すだけです.テレビも映画もゲームも,原理はまったく同じです.
ただし,ブラウザの上で動かすには書き方に 1 つだけ約束があります.
それが今回の中心になる ontimer です.
- アニメーションが「少し動かす → 描き直す → 少し待つ」の繰り返しでできていると説明できる
- ブラウザで
while True:を使ってはいけない理由を説明できる def 名前():で,動かす中身に名前を付けられる(決まり文句として.仕組みは第 13 章)ontimer(関数, ミリ秒)を使って,動き続けるプログラムが書ける- かめ自身を動かす方法と,消して描き直す方法を使い分けられる
- 画面の端で跳ね返らせる・反対側から出すことができる
- いくつものものを同時に動かせる
while True: でアニメーションを書いてはいけません.
書いた瞬間に画面が固まり,「▶ 実行」も「■ 停止」も効かなくなります.
理由は 10.1.3 で説明します.代わりに ontimer を使います.
10.1 動いて見えるしくみ
10.1.1 パラパラまんが
アニメーションと聞くと難しそうですが,正体はパラパラまんがです. 少しずつ違う絵を何枚も用意して, 次々に切り替えて見せる.それだけです.
1 コマあたりの違いはほんのわずかです. かめは 100 だけ右へずれて,22 度だけ回っただけ. それを 1 秒間に 20 回も 30 回も切り替えると,人間の目には「動いている」と見えます.
10.1.2 アニメーションの 3 ステップ
プログラムでアニメーションを作るときは,次の 3 つを繰り返します. これはどんなアニメーションでも同じです.
- 少し待つ.すぐ次のコマに行ってしまうと,速すぎて見えない.
- 1 コマぶんの処理をする.位置を少し変える,向きを少し回す,など.
- 描き直す.新しい状態を画面に出す.
この 3 つのうち,3 番目は今回ほとんど気にしなくて済みます. この教材のタートルは,命令を出せば自動で画面が更新されるからです. つまり,実際に書くのは「少し待つ」と 「1 コマぶんの処理」の 2 つです.
tracer() や
update() という命令があり,
「画面の描き直しをいつ行うか」を自分で制御できます.
この教材では tracer も update も書く必要がありません.
書いても害はありませんが,何も起きません.
10.1.3 ブラウザでは while True: が使えない
「少し待って,少し動かす,を繰り返す」と聞くと, 第 6 章で習った繰り返しを使って,こう書きたくなります.
while True: # ずっと繰り返す
t.fd(2) # 少し動かす
# ここで少し待ちたい
パソコンにインストールした Python なら,これで動きます. しかしブラウザの中では,これは絶対に使えません. 次のプログラムを実行して,何が起きるか確かめてください.
from turtle import *
t = Turtle()
t.shape("turtle")
t.pu()
# ブラウザではこう書いてはいけない
while True:
t.fd(2)
done()
しばらく画面が固まったあと, 「無限ループ(while True)になっていませんか」という エラーがコンソールに出たはずです.
while True:と書くと,Python がその 1 本を離さなくなります.- ブラウザは画面を描き直すことも,ボタンの反応を返すこともできません.
- だから画面が固まり,しかも絵は 1 枚も更新されません.
- かめは超高速で動いているのに,誰にも見えないまま,という間抜けなことになります.
解決策は「Python が作業の線をいったん返す」ことです.
少し動かしたら,作業の線をいったんブラウザに返す.
ブラウザが画面を描いたら,また少し動かす.
この「いったん返して,あとで呼んでもらう」ための命令が
ontimer です.
10.1.4 動かす中身に名前を付ける(def)
ontimer は「あとで,これを呼んでください」とお願いする命令です.
お願いするからには,「これ」が何を指すのかを言えなければいけません.
「かめを 4 歩進める」という中身に
名前を付けておく必要がある,ということです.
中身に名前を付ける書き方が def です.
def step(): # 「step」という名前を付ける.うしろの : を忘れない
t.fd(4) # 字下げした行が,名前の「中身」
ontimer(step, 30)
書き方の約束は 3 つだけです.
| 書くもの | 意味 |
|---|---|
def step(): | step という名前を付けるという合図.うしろの :(コロン)を忘れない |
| 字下げした行 | その名前の中身.if や for のときと同じで,字下げが続くところまでが中身 |
step() | その中身をいま実行する(呼び出す).かっこを付ける |
そして,ontimer に渡すときはかっこを付けません.
ここだけは,はっきり区別してください.
| 書き方 | 意味 |
|---|---|
step() | いますぐ呼ぶ.中身が実行される |
step | 名前そのもの.中身は実行されない.「この名前のものを,あとで呼んでね」と渡すときはこちら |
ontimer(step, 30) | ○ 正しい.名前だけを渡している |
ontimer(step(), 30) | × まちがい.その場で 1 回呼んでしまい,あとは動かない |
def で作るものを関数といいます.
関数は第 13 章と第 14 章でしっかり習う,大きなテーマです.
今回はまだ,その入口にも立っていません.
def 名前():と書いて,中身を字下げする名前()で呼び出すontimer(名前, 30)ではかっこを付けない
いまはこの 3 つを「決まり文句」として覚えてください.
なぜこう書くのか,名前を付けると何がうれしいのか,
「引数」や「返り値」とは何かといった仕組みは,第 13 章でしっかりやります.
今回は ontimer に渡すための入れ物として使うだけです.
global という決まり文句が後から出てきます.
これは「この関数の中で,外にある変数を書き換えます」という宣言で,
アニメーションで「今どこにいるか」を覚えておくのに使います.
くわしくは 10.3.2 で説明します.こちらも仕組みは第 13 章に送りますので,
今は「外の変数を書き換えるときのおまじない」と思ってください.
10.2 ontimer — 「◯ミリ秒後に呼んでください」
10.2.1 いちばん小さなアニメーション
まず,かめが 1 匹,右へ進んでいくだけのプログラムを見ます. これが今回いちばん重要なプログラムです.実行してください.
from turtle import *
t = Turtle()
t.shape("turtle")
t.color("seagreen")
t.shapesize(1.6, 1.6)
t.pu() # 線は引かない
t.goto(-260, 0)
def step():
t.fd(4) # 1 コマぶん,少しだけ動かす
ontimer(step, 30) # 30 ミリ秒後に,もう一度 step を呼んでもらう
ontimer(step, 30) # 最初の 1 回だけ,ここで予約する
done()
かめがゆっくり右へ進み,やがて画面の外へ出ていきます. 止めたいときは「■ 停止」を押してください.
動いているものは本のページには載せられないので, 途中の 3 コマを止めて並べた図を見てください. うすい枠は画面のふちです.
1 コマでは 4 歩しか動きません. その 4 歩を 30 ミリ秒ごとに積み重ねた結果が,この動きです.
10.2.2 ontimer の読み方
ontimer は,予約を入れる命令です.
ontimer(step, 30) # ~~~~ ~~ # 関数 ミリ秒
| 書くもの | 意味 |
|---|---|
第 1 引数 step | あとで呼んでほしい関数の名前.かっこは付けない(ここが最大の注意点) |
第 2 引数 30 | 何ミリ秒後に呼ぶか.1000 ミリ秒 = 1 秒 |
つまり ontimer(step, 30) は,
「30 ミリ秒たったら step という関数を呼んでください」という
ブラウザへのお願いです.お願いしたらすぐ次の行に進みます.
その場で 30 ミリ秒待つわけではありません.
ontimer(step(), 30) と書いてはいけません.
step() とかっこを付けると,その場で関数を呼んでしまい,
その返り値(この場合は None)を
ontimer に渡すことになります.
渡すのは「関数そのもの」なので,かっこは付けません.
10.1.4 で決まり文句として覚えた「名前() は呼ぶ,名前 は渡す」の
区別が,ここで効いてきます.なぜそう区別するのかは第 13 章でくわしく習います.
10.2.3 なぜ繰り返しになるのか
while も for も使っていないのに,
なぜ動き続けるのでしょうか.答えは,
step の最後でもう一度 ontimer を呼んでいるからです.
1 周するたびにいったんブラウザに戻るのがポイントです.
その 30 ミリ秒のあいだに,ブラウザは画面を描き直し,
ボタンが押されていないかを確かめ,それから step を呼びます.
だから画面が固まらないのです.
| 書き方 | 何が起きるか |
|---|---|
step の中の ontimer を書き忘れる | 1 コマだけ動いて止まる.予約が途切れるため |
step の中で ontimer を 2 回呼ぶ | 予約が毎回 2 倍に増える.すぐに手がつけられない速さになる |
最後の行の ontimer(step, 30) を書き忘れる | まったく動かない.最初の 1 回を誰も予約していない |
ontimer(step(), 30) | その場で step が 1 回呼ばれ,あとは動かない |
10.2.4 Screen().ontimer でもよい
本や Web で調べると,次のような書き方も出てきます.
screen = Screen() screen.ontimer(step, 30)
ontimer はもともと画面(スクリーン)の命令なので,
こちらが本来の姿です.ただし from turtle import * と書いていれば,
ontimer(step, 30) と短く書いてもまったく同じです.
この教材ではどちらでもかまいません.
短いほうを使うことにします.
fd(100) は名前のないかめへの命令だった」と習いました.
それと同じで,ontimer(step, 30) は
「名前のない画面への命令」です.
Screen() と書くと,その画面に名前をつけて受け取れます.
赤い玉が画面の上から下へ落ちていくアニメーションです.
____ を埋めてください.
向きは seth で決めます.0 が東,90 が北,180 が西,270 が南でした.
from turtle import *
t = Turtle()
t.shape("circle")
t.color("crimson")
t.shapesize(1.5, 1.5)
t.pu()
t.goto(0, 200)
t.seth(____) # 下(南)を向かせる
def step():
t.____(3) # 少しだけ前へ進む
ontimer(____, 40) # 40 ミリ秒後にもう一度 step を呼ぶ
ontimer(step, 40) # 最初の 1 回を予約する
done()
10.3 動かし方は 2 通りある
「動かす」には,まったく違う 2 つのやり方があります. どちらも大事なので,順番に見ていきます.
10.3.1 かめ自身を動かす(スプライト方式)
10.2 節でやったのがこれです.かめの姿そのものを動かします.
pu() してあるので線は引かれず,かめだけがすーっと動きます.
ゲームのキャラクターのような動かし方なので,
スプライト方式と呼ぶことにします.
from turtle import *
ht()
t = Turtle()
t.shape("turtle")
t.color("darkorange")
t.shapesize(2, 2)
t.pu()
def step():
t.lt(3) # 少し左に回る
t.fd(4) # 少し進む
ontimer(step, 30)
ontimer(step, 30)
done()
lt(3) と fd(4) を繰り返すので,かめは円を描いて回り続けます.
第 2 章で「曲線は短い直線をつないだもの」と習ったのと,同じ考え方です.
t.pd() に変えてみると軌跡が残ります.試してみてください.
図では,通った道をうすい線で描いておきました.
実際のプログラムでは pu() してあるので,この線は残りません.
かめだけが,この道筋をなぞって動きます.
pu() を書いて,ペンを上げた状態から始めます.
はじめから上がっているわけではないので,
自分で pu() と書く必要があります.
10.3.2 消して描き直す(クリア方式)
かめの姿は 7 種類しかありません. 自分で描いた絵を動かしたいときはどうするか. 答えは,パラパラまんがそのままです.
clear()で画面を消す- 少しずれた場所(向き・大きさ)に描き直す
ontimerで次のコマを予約する
ここでも 10.1.4 の def が役に立ちます.
「家を 1 軒描く」に house という名前を付けておけば,
あとは少しずつ違う場所・向きで呼ぶだけでアニメーションになります.
def house(x, y, a): は,かっこの中に
x, y, a と 3 つの名前が入っています.
これは「描く場所と傾きを,呼ぶたびに変えられるようにする」ための仕組みで,
引数といいます.
house(-40, -40, 30) と呼べば,x が -40,
y が -40,a が 30 になって中身が動く,と思ってください.
いまはこれも決まり文句で,仕組みは第 13 章でくわしく習います.
from turtle import *
ht() # 既定のかめの姿を消す(線は描ける)
pensize(3)
pencolor("teal")
kaku = 0 # 家の傾き
def house(x, y, a):
"""(x, y) を左下の角として,a 度傾いた家を 1 軒描く"""
pu()
goto(x, y)
seth(a)
pd()
lt(60)
fd(80)
rt(120)
fd(80)
rt(120)
fd(80)
lt(90)
fd(80)
lt(90)
fd(80)
lt(90)
fd(80)
lt(90)
fd(80)
pu()
def step():
global kaku
clear() # ① 消す
house(-40, -40, kaku) # ② 少し傾けて描き直す
kaku = kaku + 5
ontimer(step, 60) # ③ 次のコマを予約する
ontimer(step, 60)
done()
clear() は,描いた線をすべて消す命令です.
消してから描き直すので,家が 1 軒だけ回っているように見えます.
clear() を消してから実行すると,
家が重なって花のような模様になります.それも面白いので試してください.
step の 1 行目に global kaku と書いてあります.
これは「関数の外にある変数 kaku を書き換えます」という宣言です.
- 関数の中で変数を読むだけなら
globalは要りません. - 関数の中で
kaku = kaku + 5のように書き換えるときはglobalが必要です. - 書き忘れると,Python は「関数の中だけの新しい
kaku」を作ってしまい,外のkakuはずっと 0 のままになります(UnboundLocalErrorというエラーになることもあります).
アニメーションでは「今どこにいるか」「今どれだけ傾いているか」といった
状態を関数の外に置き,毎コマ書き換えます.
だから global がよく登場します.
def と同じで,global も
いまは決まり文句と思ってください.
「関数の中で書き換える外の変数は,1 行目に global で並べておく」.
これだけ覚えておけば今回は困りません.
なぜ関数の中と外で変数が別ものになるのか(その仕組みを「スコープ」といいます)は,
第 13 章でくわしく習います.
10.3.3 どちらを使うか
| スプライト方式 | クリア方式 | |
|---|---|---|
| 動かすもの | かめの姿そのもの | 自分で描いた絵 |
| 毎コマやること | t.fd(4) など少し動かすだけ | clear() してから全部描き直す |
| 見た目 | shape の 7 種類に限られる | 何でも描ける |
| 速さ | 軽い.たくさん動かせる | 重い.描く線が多いと遅くなる |
| 向いている用途 | ボール・弾・キャラクター | 回る家・大きくなる星・パラパラまんが |
| ペン | pu() しておく | pd() で描く |
clear() はすべての線を消すので,
残したい線があるときは注意してください
(かめの姿と stamp は clear() では消えません).
正方形が少しずつ大きくなっていくアニメーションです.
クリア方式で書きます.____ を埋めてください.
ookisa を関数の中で書き換えるので,忘れてはいけない宣言があります.
from turtle import *
ht()
pensize(3)
pencolor("royalblue")
ookisa = 20
def square(s):
"""1 辺 s の正方形を中央に描く"""
pu()
goto(-s / 2, -s / 2)
seth(0)
pd()
for i in range(4):
fd(s)
lt(90)
pu()
def step():
____ ookisa # 外の変数を書き換える宣言
____() # 前のコマを消す
square(ookisa)
ookisa = ookisa + 4
ontimer(step, ____) # 40 ミリ秒後に次のコマ
ontimer(step, 40)
done()
10.4 速さを決める
10.4.1 待ち時間と 1 コマの移動量
アニメーションの速さは,2 つの数の組み合わせで決まります.
ontimerのミリ秒… 1 秒に何コマ進むかfdや+ 5の大きさ… 1 コマでどれだけ動くか
| ontimer のミリ秒 | 1 秒あたりのコマ数 | 感じ |
|---|---|---|
1000 | 1 コマ | カチ,カチ.時計の秒針 |
200 | 5 コマ | かくかく.紙芝居 |
100 | 10 コマ | 少しぎこちない |
50 | 20 コマ | だいたい滑らか |
33 | 約 30 コマ | テレビと同じくらい.おすすめ |
16 | 約 60 コマ | とても滑らか.ただし重い処理には不向き |
同じ速さを,2 通りに作れることに注意してください. 「30 ミリ秒ごとに 4 ずつ」と「60 ミリ秒ごとに 8 ずつ」は, どちらも 1 秒で約 133 進みます.しかし見た目は違います. 前者のほうが滑らかで,後者はカクカクします. 待ち時間は短めに,1 コマの移動量は小さめにするのが基本です.
次の 2 枚は,1 秒ぶんのコマを全部かさねて描いたものです. 玉ひとつが 1 コマの位置を表しています.
1 秒で進む距離は同じなのに,コマの粒が違います. 左はすき間がなく,右はとびとびです. このすき間が,見たときの「カクカク」の正体です.
10.4.2 速くしすぎてはいけない
では ontimer(step, 1) と書けば究極に滑らかになるでしょうか.
なりません.コンピュータの計算が追いつかなくなるからです.
とくにクリア方式では,毎コマ絵を全部描き直しています. 線が 100 本ある絵なら,1 秒間に 100 × 30 = 3000 本描くことになります. これ以上速くしても,実際のコマ数は増えず,ただ重くなるだけです.
ontimer(step, 30) あたりから始めて,
速すぎたら数を大きく,遅すぎたら小さくして調整してください.
10 より小さい数は,書いても意味がないことが多いです.
ontimer を余分に呼んでいます.
step の中の ontimer がちょうど 1 つか,
数えてみてください.if の中と外の両方に書いてしまう間違いが多いです.
10.5 画面の端で跳ね返らせる
10.5.1 位置を自分で持つ
10.2 節では t.fd(4) で動かしました.
これは楽ですが,「今どこにいるか」を自分で把握していないので,
端に着いたかどうかの判定がしにくくなります.
そこで,位置と速度を自分の変数で持つやり方に切り替えます.
x = 0 # 今の x 座標
dx = 5 # 1 コマで進む量(速度)
def step():
global x
x = x + dx # 位置を進める
t.goto(x, 0) # その座標へ動かす
ontimer(step, 30)
dx という名前は「x の変化量(difference)」の意味です.
毎コマ x に dx を足すので,
dx がそのまま速度になります.
t.fd(5) は「今向いている方向へ」進む命令でした.
ですから,かめを右へ動かすには,まず右を向かせなければなりません.
しかし右を向かせると,姿も一緒に右を向いてしまいます.
上を向いたまま右へ動かしたいときは,次の 2 通りがあります.
t.rt(90)で右を向いてt.fd(5),そのあとt.lt(90)で向きを戻すt.goto(x, y)で座標そのものを書き換える(向きは変わらない)
1 は向きを戻し忘れると絵がくずれます. 2 は向きに関係なく位置だけを変えられるので,考えることが減ります. この教材では 2 を使います.
t.xcor() で今の x 座標が返ってきます
(第 8 章).どちらの方法でもよいのですが,自分の変数で持つほうが,
速度(dx)も一緒に扱えて分かりやすいので,
この教材では変数で持つ方法を使います.
10.5.2 符号を反転して跳ね返す
右へ進んでいるものを左へ向かわせるには,
速度の符号を反転するだけで済みます.
dx が 5 なら -5 にする.たったそれだけです.
if x > 290:
dx = -dx # 右端に着いたら,向きを逆にする
dx = -dx は「今の dx の符号を入れ替えて,
もう一度 dx に入れる」という意味です.
5 なら -5 に,-5 なら 5 に戻ります.
上下も同じです.y と dy を用意して,
上端・下端で dy = -dy とすれば,
画面の中で跳ね回るボールができます.
from turtle import *
ht()
t = Turtle()
t.shape("circle")
t.color("crimson")
t.shapesize(1.5, 1.5)
t.pu()
x = 0 # 位置
y = 0
dx = 6 # 速度
dy = 4
def step():
global x, y, dx, dy
# 位置を進める
x = x + dx
y = y + dy
# 左右の壁に当たったら跳ね返る
if x > 290 or x < -290:
dx = -dx
# 上下の壁に当たったら跳ね返る
if y > 200 or y < -200:
dy = -dy
t.goto(x, y)
ontimer(step, 25)
ontimer(step, 25)
done()
図ではボールの通った道をうすい線で描いてあります.
dx や dy の符号が入れ替わったところが,
ちょうど「く」の字に折れているのが分かります.
画面は横 620・縦 460 なので,中央から見て右端は x が約 310,
上端は y が約 230 です.そこより少し内側の 290・200 で跳ね返らせています.
dx と dy の数を変えると,
飛ぶ角度と速さが変わります.いろいろ試してください.
t.pd() を足して軌跡を残すと,
ボールの通り道が幾何学模様になります.
dx と dy の比によって模様が変わるので,
作品として面白いものになります.
10.5.3 端まで行ったら反対側から出す
跳ね返らせるのではなく,右端まで行ったら左端から出てくる という動かし方もあります.「流れ星」はこのやり方で作ります. こちらは符号を反転するのではなく,座標そのものを書き換えます.
if x > 310:
x = -310 # 右端に着いたら左端へ瞬間移動
from turtle import *
ht()
pensize(3)
pencolor("gold")
x = -250 # 星の位置
y = 180
a = 0 # 星の傾き
def star(cx, cy, size, ang):
"""(cx, cy) から,ang 度傾いた大きさ size の星を描く"""
pu()
goto(cx, cy)
seth(ang)
pd()
for i in range(5):
fd(size)
rt(144)
pu()
def step():
global x, y, a
clear()
star(x, y, 60, a)
x = x + 8 # 右へ
y = y - 5 # 下へ
a = a + 6 # 少し回す
# 右端まで行ったら左端へ
if x > 300:
x = -300
# 下端まで行ったら上端へ
if y < -200:
y = 200
ontimer(step, 40)
ontimer(step, 40)
done()
右下へ流れながら回転し,画面から出ると反対側から入ってきます. この流れ星は,次の順番で作ると途中で迷子になりません.
- 星を描く関数を作り,まず 1 個だけ描いて動くことを確かめる
ontimerで,星を回すだけにしてみる- 座標
xを足して,右に動かす - 右端まで行ったら左端に戻す
yも足して,下にも動かす- 下端まで行ったら上端に戻す
- 星の大きさを変える(
sizeの引数) - 星を増やす(変数を 2 組用意する)
青い四角が,画面の左右をいつまでも往復します.
____ を埋めてください.
書き換える変数が 2 つあるので,global の行にも穴があります.
from turtle import *
ht()
t = Turtle()
t.shape("square")
t.color("royalblue")
t.shapesize(2, 2)
t.pu()
x = -200 # 位置
dx = 7 # 速度
def step():
global x, ____
x = x + dx
if x > 290:
dx = ____ # 右端で向きを逆にする
if x < ____:
dx = -dx # 左端で向きを逆にする
t.goto(x, 0)
ontimer(step, 25)
ontimer(step, ____)
done()
10.6 いくつも同時に動かす
10.6.1 1 つの step でまとめて動かす
2 つ以上のものを動かす,いちばん簡単な方法は,
1 つの step の中に全部書くことです.
第 8 章の「交互に命令を出す」と同じ考え方です.
from turtle import *
ht()
a = Turtle()
a.shape("turtle")
a.color("seagreen")
a.shapesize(1.6, 1.6)
a.pu()
a.goto(-280, 80)
b = Turtle()
b.shape("circle")
b.color("crimson")
b.shapesize(1.6, 1.6)
b.pu()
b.goto(280, -80)
b.seth(180)
def step():
a.fd(5) # かめは速い
b.fd(2) # 玉は遅い
ontimer(step, 30)
ontimer(step, 30)
done()
a.fd(5) と b.fd(2) のように
1 コマで動く量を変えると,速さの違いが出ます.
ontimer は 1 つしかないので,コマ数は 2 つとも同じです.
どちらも同じ step の中で動いているので,
コマを刻む回数はまったく同じです.
違うのは 1 コマで進む歩数だけです.
10.6.2 ontimer を 2 本立てる
コマ数そのものを変えたいときは,
step を 2 つ作って,それぞれに予約を入れます.
2 本の予約は互いに関係なく回り続けます.
from turtle import *
ht()
a = Turtle()
a.shape("turtle")
a.color("seagreen")
a.shapesize(1.6, 1.6)
a.pu()
a.goto(-120, 0)
b = Turtle()
b.shape("circle")
b.color("crimson")
b.shapesize(1.6, 1.6)
b.pu()
b.goto(120, 0)
def step_a():
a.lt(4)
a.fd(6)
ontimer(step_a, 20) # 20 ミリ秒ごと(速い)
def step_b():
b.rt(2)
b.fd(3)
ontimer(step_b, 60) # 60 ミリ秒ごと(遅い)
ontimer(step_a, 20)
ontimer(step_b, 60)
done()
step_a の中では step_a を,
step_b の中では step_b を予約しています.
ここを取り違えると,片方が止まったり,倍速になったりします.
| やり方 | 向いている場面 |
|---|---|
1 つの step にまとめる | ぶつかり判定など,お互いの位置を見たいとき.動きがそろっていてよいとき |
ontimer を複数立てる | コマ数そのものを変えたいとき.「背景は 1 秒に 2 回,主人公は 30 回」のように役割が違うとき |
緑のかめと赤のかめが,同時に左から右へ走ります. ____ を埋めてください. b は a より遅く走らせます.
from turtle import *
ht()
a = Turtle()
a.shape("turtle")
a.color("seagreen")
a.shapesize(1.6, 1.6)
a.pu()
a.goto(-270, 60)
b = Turtle()
b.shape("turtle")
b.color("crimson")
b.shapesize(1.6, 1.6)
b.pu()
b.goto(-270, -60)
def step():
a.fd(6)
____.fd(____) # b は a より遅く
ontimer(____, 30) # 30 ミリ秒後にもう一度
ontimer(step, ____)
done()
10.7 回す・大きくする・描き足す
10.7.1 その場で回す
いちばん作りやすいアニメーションが, 「今まで作った絵を回す」です. クリア方式と,角度を持つ変数 1 つでできます.
from turtle import *
ht()
pensize(4)
pencolor("gold")
kaku = 0 # 星の傾き
def star(size, ang):
pu()
goto(0, 0)
seth(ang)
pd()
for i in range(5):
fd(size)
rt(144)
pu()
def step():
global kaku
clear()
star(160, kaku)
kaku = kaku + 5
ontimer(step, 50)
ontimer(step, 50)
done()
kaku を + 5 ではなく - 5 にすれば逆回りです.
+ 20 にすれば速く回ります.
描く図形を,自分が第 2 章〜第 8 章で作った絵に差し替えてみてください.
10.7.2 大きくする・小さくする
大きさを持つ変数を 1 つ用意し,毎コマ増やします. 大きくなりすぎたら,今度は減らす. これは 10.5.2 の跳ね返りとまったく同じ仕掛けです.
from turtle import *
ht()
pensize(3)
pencolor("teal")
ookisa = 20 # 今の大きさ
d = 4 # 1 コマで変わる量
def box(s):
"""1 辺 s の正方形を中央に描く"""
pu()
goto(-s / 2, -s / 2)
seth(0)
pd()
for i in range(4):
fd(s)
lt(90)
pu()
def step():
global ookisa, d
clear()
box(ookisa)
ookisa = ookisa + d
# 大きくなりすぎたら縮め始める
if ookisa > 300:
d = -d
# 小さくなりすぎたら膨らませ始める
if ookisa < 20:
d = -d
ontimer(step, 40)
ontimer(step, 40)
done()
def に「大きさ」を渡すことで,
同じことを自分で実現します.
def box(s): の s がその「大きさ」で,
これも 10.3.2 で触れた引数です(仕組みは第 13 章でくわしく習います).
かめの姿を大きくしたいときは話が別で,
第 8 章で習った t.shapesize(縦, 横) が使えます.
毎コマ t.shapesize(s, s) と呼べば,かめが大きくなっていきます.
10.7.3 少しずつ描き足す(描いている途中を見せる)
ここまでは,毎コマ clear() して描き直していました.
消さずに,1 コマにつき 1 本だけ描き足すとどうなるでしょうか.
絵が目の前で少しずつできあがっていくアニメーションになります.
もうひとつ新しいのは,終わりがあることです.
これまでのプログラムは step の最後で必ず ontimer を
呼んでいたので,永久に動き続けました.
if の中でだけ予約すれば,条件を満たさなくなったところで
予約が途切れ,アニメーションが止まります.
from turtle import *
ht()
# 題名を先に書いておく
pu()
goto(0, 150)
pencolor("#334155")
write("かめが星をえがきます", align="center", font=("Arial", 20, "bold"))
t = Turtle()
t.shape("turtle")
t.color("seagreen")
t.shapesize(1.4, 1.4)
t.pensize(4)
t.pencolor("gold")
t.pu()
t.goto(-90, -40)
t.seth(0)
t.pd()
nokori = 5 # あと何本引くか
def step():
global nokori
if nokori > 0:
t.fd(180) # 1 コマにつき 1 本だけ描き足す
t.rt(144)
nokori = nokori - 1
ontimer(step, 600) # まだ残っているので,次を予約する
else:
t.ht() # 描き終わったら,かめを消して
t.pu()
t.goto(0, -150)
t.pencolor("crimson")
t.write("かんせい!", align="center", font=("Arial", 28, "bold"))
# ここでは ontimer を呼ばない → アニメーションが止まる
ontimer(step, 600)
done()
write は第 11 章でくわしく習う命令です.
ここでは「画面に文字を書く」とだけ覚えておいてください.
アニメーションに題名や「かんせい!」の一言があるだけで,
ぐっと作品らしくなります.
clear() を呼びません.
そのぶん毎コマの仕事が 1 本だけなので軽いのですが,
いちど描いた線は動かせません.
「できあがっていく様子を見せたい」ときだけ使ってください.
動かしたいものがあるなら,10.3 のどちらかの方式を選びます.
10.8 この章で出てきた命令
| 命令 | 意味 |
|---|---|
ontimer(関数, ミリ秒) | そのミリ秒後に,その関数を呼んでもらう予約を入れる.関数名にかっこは付けない |
Screen().ontimer(関数, ミリ秒) | 上と同じ.どちらで書いてもよい |
clear() | 描いた線をすべて消す.描き直す前に使う |
global 変数名 | 関数の中で,関数の外の変数を書き換えるときに書く |
t.goto(x, y) | かめをその座標へ動かす(毎コマ呼ぶ) |
t.seth(角度) | かめの向きを決める.0 が東,90 が北 |
t.shapesize(縦, 横) | かめの姿の大きさ(アニメで変えてもよい) |
t.xcor() / t.ycor() | かめに今の座標を聞く |
t.pu() | アニメーションでは基本的にペンを上げておく |
write(文字, align=, font=) | 画面に文字を書く.題名や「かんせい!」を出すのに使う(くわしくは第 11 章) |
ht() | 既定のかめの姿を消す.線は描ける |
done() | プログラムの終わり(予約は残り続ける) |
表の中でうっかり忘れやすいのが ht() です.
書かないと,何も動かしていない既定のかめが画面に残り,
自分で描いた絵の上に重なって見えます.
while True:… 使えません.画面が固まります.time.sleep(0.1)… 使えません.同じ理由です.待つ役目はontimerが引き受けます.tracer()/update()… 書く必要がありません.この教材では自動で画面が更新されます.
演習
以下の演習には,出発点となるプログラムが入っています. 「▶ この演習を開く」で右側に読み込み,それを発展させて作品にしてください. できあがったら「⤓ 保存」でファイルに保存して,手元に残しておきましょう.
- 最後の行に
ontimer(step, 30)と書いたか(最初の 1 回の予約) stepの中にもontimerを書いたか(次の予約)- 関数名にかっこを付けていないか(
stepであってstep()ではない)
星がその場で回り続けるプログラムを与えます. これをもとに,独自にアレンジを加えたプログラムを作ってください.
- 回す図形を,第 2 章〜第 8 章で自分が描いた絵に差し替える
- 回す速さ・向きを変える(
kakuの増やし方) - 回しながら大きさも変える(10.7.2 と組み合わせる)
- 図形を 2 つ描き,逆向きに回す
- 回りながら色が変わるようにする
星が右下へ流れ,端まで行くと反対側から出てくるプログラムを与えます. これをもとに,独自にアレンジを加えたプログラムを作ってください.
- 星を2 個・3 個に増やす(変数を 2 組・3 組用意する)
- 星ごとに速さ・大きさ・色を変える
- 流れる向きを変える(
xを減らす,yを増やす) - 端まで行ったら,でたらめな位置から出てくるようにする(
from random import randint) - 星ではなく,自分の描いた絵を流す
人がハードルを跳びながら走っていくプログラムを与えます.
person(人を描く関数)と hurdle(ハードルを描く関数)を
組み合わせ,人の x 座標に応じて y 座標を持ち上げることで「跳ぶ」を表しています.
これをもとに,独自にアレンジを加えたプログラムを作ってください.
- ハードルの数と位置を増やす
- 跳ぶ高さ・跳び始める距離を変える
- 走る人を 2 人にして競走させる
- 人の絵をもっと作り込む(手足の形を変える)
- ゴールのテープを描き,たどり着いたら
printで知らせる
ボールが画面の中で跳ね回るプログラムを与えます.
ボールを 3 個以上に増やしてください.
変数を 1 個ぶんまるごとコピーして,名前を x2・y2・
dx2・dy2 のように変えるのがコツです.
- ボールを 3 個以上にする(必須)
- ボールごとに色・大きさ・速さを変える
t.pd()にして,軌跡で模様を描かせる- ボール同士の距離を
distanceで測り,近づいたら色を変える(第 8 章) - 跳ね返るときに少し速さを落として,だんだん止まるようにする
global の行に,増やした変数を全部書き足すのを
忘れないでください.global x, y, dx, dy, x2, y2, dx2, dy2 のようになります.
コマを切り替えるだけのアニメーションです.
顔の口の形が 3 通りに変わるプログラムを与えます.
剰余(%)で「0 → 1 → 2 → 0 → …」と
コマ番号を回しているところが肝です.
これをもとに,独自にアレンジを加えたプログラムを作ってください.
- コマを 4 枚・5 枚に増やす(
% 3を% 5にする) - 口だけでなく,目や輪郭も変える
- 顔以外のもの(歩く人,開く花,育つ木)にする
- 切り替えの速さ(
ontimerのミリ秒)を変える - コマの切り替えと,位置の移動を同時にやる
独自の観点で,アニメーションのプログラムを作ってください. 第 2 章から第 8 章までに作った絵を動かすのでもかまいません.
- 2 つ以上のものが動くこと
- 端の処理(跳ね返る・反対側から出る・止まる)を入れること
- スプライト方式とクリア方式のどちらか(または両方)を使うこと
- 先頭にコメントで,題名・氏名・日付を書くこと
- 速さを調整して,見やすいアニメーションにすること
まとめ
- アニメーションは「少し動かす → 描き直す → 少し待つ」の繰り返し.パラパラまんがと同じ.
- ブラウザでは
while True:が使えない.画面が固まり,絵も更新されない. def 名前():と字下げで,動かす中身に名前を付ける.呼ぶときは名前(),渡すときは名前.いまは決まり文句で,仕組みは第 13 章で習う.ontimer(関数, ミリ秒)は「そのミリ秒後に呼んでください」という予約.関数名にかっこは付けない.- 関数の最後でもう一度
ontimerを呼ぶと繰り返しになる.書き忘れると 1 コマで止まり,2 回書くと倍速になる. - 動かし方は 2 通り.スプライト方式(かめ自身を動かす)とクリア方式(
clear()して描き直す). clear()せずに 1 コマ 1 本だけ描き足すと,絵ができあがっていく様子を見せられる.ifの中だけで予約すれば,終わりのあるアニメーションになる.- 向きを変えずに動かしたいときは
t.goto(x, y).t.fd()は「今向いている方向へ」しか進めない. - 位置と速度を変数で持ち,関数の中で書き換えるときは
globalを書く(これも決まり文句.仕組みは第 13 章).端ではdx = -dxで跳ね返り,x = -310で反対側から出る. - 同時に動かすには,1 つの
stepにまとめるか,ontimerを複数立てる. tracerとupdateは,この教材では書かなくてよい.
次の回では,画面に文字を出し,見た目を整えます.
write で得点や残り時間を表示し,shape と
stamp で画面をにぎやかにする方法を習います.
アニメーションに点数が付くと,一気にゲームらしくなります.