アナログ時計(その1)


*ここに掲載している「アナログ時計」のソースコードを、以下にアップロードしています。ご自由にお使いください。
Microsoft Small Basic Program Listing(Program Listing : GZH322)

秒針、短針、長針がぐるぐる回る、アナログ時計を作ってみます。
カウントダウンタイマーの時に使った、1000ミリ秒(=1秒)ごとにプログラムを実行する、

Timer.Interval = 1000
Timer.Tick = countdownevent

これを流用すれば針の動きを作れそうです。

まずは秒針を考えてみます。
1. 秒針を描画する。
2. 1秒後に、秒針が1秒分動く動きを作る。

1.はそんなに難しくなさそう。
問題は2.です。
秒針は1分間に360度回転するので、1秒間では360÷60=6度回転します。
秒針を6度回転させるには、

Shapes.Rotate(shapeName, angle)

指定された名前の図形を指定された角度に回転させるShapes.Rotateを使うことになるでしょう。
shapeNameは図形の名前、angleが回転角度になるので、ここに6を入力することになりそうです。

また、時計の秒針は、「今の秒×6度」傾いた状態であるはずです。
6時10分50秒なら、50×6=300度傾いていますよね。
だから、プログラムを実行したその瞬間、描画した秒針は300度傾いているはず。
パソコンの秒を取得するには「Clock.Second」です。
まず、「プログラムを実行した瞬間の秒針の状態を描画」するプログラム部分を考えてみました。

GraphicsWindow.Title = "Analog"
GraphicsWindow.Width = 600
GraphicsWindow.Height = 600

GraphicsWindow.BrushColor = "blue"
byousin = Shapes.AddRectangle(3, 300)
Shapes.Move(byousin, 200, 200)
byounow = Clock.Second
Shapes.Rotate(byousin, byounow * 6)

最初の3行でウィンドウを作成し、4行目で秒針の色を青に指定。
秒針の図形を「byousin」という名前にして、幅3ピクセル、長さ300ピクセルの四角形にしました。
「byousin = Shapes.AddRectangle(3, 300)」は「図形の形を指定した」だけですので、その次の「Shapes.Move(byousin, 200, 200)」で秒針をウィンドウの中ほどに移動しています。
「byounow = Clock.Second」で、現在の秒を取得。
そして「Shapes.Rotate(byousin, byounow * 6)」で、図形「byousin」を傾けてみました。

秒針を描くための、

GraphicsWindow.BrushColor = "blue"
byousin = Shapes.AddRectangle(3, 300)
Shapes.Move(byousin, 200, 200)
byounow = Clock.Second
Shapes.Rotate(byousin, byounow * 6)

は、サブルーチンの一部分になりそうですね。

図形「byousin」を1秒毎に回転させるにはどうするか?
タイマーイベントと、サブルーチンを考えます。

Timer.Interval = 1000
Timer.Tick = tokeimove

Sub tokeimove
  GraphicsWindow.Clear()
  GraphicsWindow.BrushColor = "blue"
  byousin = Shapes.AddRectangle(3, 300)
  Shapes.Move(byousin, 200, 200)
  byounow = Clock.Second
  Shapes.Rotate(byousin, byounow * 6)
EndSub

秒針を描く文章に、「GraphicsWindow.Clear()」を付け加えただけです。
GraphicsWindow.Clear()」は、デジタル時計の時と同じく、「前に描いた秒針を消してから、新しい秒針を描く」ために入れました。
これをやらないと無数の秒針が描画されてしまいます。

座標を(200, 200)にしたのは「とりあえず」です。
回転といっても、図形のどこを中心にして回転するのかわからなかったので、とりあえずこう指定してみたというだけです。

以上を繋げてみます。

GraphicsWindow.Title = "Analog"
GraphicsWindow.Width = 600
GraphicsWindow.Height = 600

Timer.Interval = 1000
Timer.Tick = tokeimove

Sub tokeimove
  GraphicsWindow.Clear()
  GraphicsWindow.BrushColor = "blue"
  byousin = Shapes.AddRectangle(3, 300)
  Shapes.Move(byousin, 200, 200)
  byounow = Clock.Second
  Shapes.Rotate(byousin, byounow * 6)
EndSub

これを実行するとどうなるか。
実際にやってみるとわかりますが、棒(四角形)の重心を中心にしてぐるぐる回ります。
これじゃなーい!w
でもまあ、1秒毎に動く棒自体は作れました。
もうちょっと手を入れねばなりません。
Shapes.Move(byousin, 200, 200)における、座標(200, 200)はどう指定すれば良いのでしょうか。
この、重心部分を秒数によって移動させないといけないような気がします。

……これは恐らく、三角関数の問題じゃないかと。
問題を簡単にするために、「長さ2の棒を角度θだけ回転させる」ことを考えます。
長さ2の棒の重心は中心部分、丁度長さ1のところのはず。

時計1

私の三角関数の知識が間違っていなければ、重心はこんな計算で求められるはずです。
つまり、「重心を中心に角度θ回転した後に、x座標はsinθ足して、y座標は(1 - cosθ)引く」と、棒の一番端を中心点にして角度θ回ったかのように見えるはずなのです。
……ということは三角関数を求める関数を探さねばなりませんね。
Documentation Referenceを探してみたらありました。

> Math.Cos(angle)
> ラジアンで与えられた角度に対応するコサインを取得します。
> 
> Math.Sin(angle)
> ラジアンで与えられた角度に対応するサインを取得します。

ラジアンじゃないと駄目なのか。
度数法でθ度=θ×π÷180ラジアンになりますので、これも考慮して式を作らないといけません。
ちなみに円周率πは、「Math.Pi」で与えられると同ページに記載されていました。

あと、秒針の長さ・太さと、秒針が回転する中心点の座標を変数で指定しておく方が、後々でミスしないで済みそうですね。

GraphicsWindow.Title = "Analog"
GraphicsWindow.Width = 600
GraphicsWindow.Height = 600

byousinfutosa = 3
byousinnagasa = 250
tyusinX = 300
tyusinY = 300

秒針の太さを「byousinfutosa」、秒針の長さを「byousinnagasa」、中心点の座標を(tyusinX ,tyusinY)にしました。
そうすると、「Shapes.Move(byousin, 200, 200)」が、

Shapes.Move(byousin, tyusinX - byousinfutosa, tyusinY - byousinnagasa)

に変更になるのがわかりますでしょうか。

また、「重心を中心に角度θ回転した後に、x座標はsinθ足して、y座標は(1 - cosθ)引く」は、「棒の長さ=2」で計算した結果です。
「棒の長さ=byousinnagasa」だったら、「重心を中心に角度θ回転した後に、x座標はbyousinnagasa/2×sinθ足して、y座標はbyousinnagasa/2×(1 - cosθ)引く」に変更になります。
(ただし、これまでも何度か書いている通り、GraphicsWindowで描いたウィンドウは、y軸の下方向が正なので、プログラム上では「y座標はbyousinnagasa/2×(1 - cosθ)足す」になります)

sinθとcosθを、ラジアン表記でどうするか? は、計算すればわかります。

byounow = Clock.Second
pai = Math.Pi

と変数を決めたら、

sinθ → Math.Sin(byounow * 6 * pai / 180)
cosθ → Math.Cos(byounow * 6 * pai / 180)

これで合っているはず。
以下でまとめてみました。

GraphicsWindow.Title = "Analog"
GraphicsWindow.Width = 600
GraphicsWindow.Height = 600

byousinfutosa = 3
byousinnagasa = 250
tyusinX = 300
tyusinY = 300

Timer.Interval = 1000
Timer.Tick = tokeimove

Sub tokeimove
  GraphicsWindow.Clear()
  GraphicsWindow.BrushColor = "blue"
  byousin = Shapes.AddRectangle(byousinfutosa, byousinnagasa)
  byounow = Clock.Second
  pai = Math.Pi
  idoux = byousinnagasa * 0.5 * Math.Sin(byounow * 6 * pai / 180)
  idouy = byousinnagasa * 0.5 * (1 - Math.Cos(byounow * 6 * pai / 180) )
  Shapes.Rotate(byousin, byounow * 6)
  Shapes.Move(byousin, tyusinX - byousinfutosa + idoux, tyusinY - byousinnagasa + idouy)
EndSub

以下、説明です。
サブルーチンの、

  idoux = byousinnagasa * 0.5 * Math.Sin(byounow * 6 * pai / 180)
  idouy = byousinnagasa * 0.5 * (1 - Math.Cos(byounow * 6 * pai / 180) )

は、
「重心を中心に角度θ回転した後に、x座標はbyousinnagasa/2×sinθ足して、y座標はbyousinnagasa/2×(1 - cosθ)引く」
の、「byousinnagasa/2×sinθ」「byousinnagasa/2×(1 - cosθ)」の計算部分です。これにそれぞれ「idoux」「idouy」と名前を付けました。

そして、「Shapes.Move(byousin, tyusinX - byousinfutosa, tyusinY - byousinnagasa)」に、「idoux」「idouy」を足せば完成になります。

三角関数の計算になるとは思わなかった……w
とにかく秒針が出来たので、後は分、時の針を作りましょう。
ここまで長くなったので、次ページへ。

次:アナログ時計(その2)


▲TOPへ戻る