砂時計


「カウントダウンタイマー」の応用として、砂時計を作ってみます。
1分でも3分でも構いませんが、砂時計でカウントダウンさせます。
もちろん、砂時計の図形と、砂が動いていくアニメーションっぽいものも付けます。
ひとまず、1分で砂が全て落ちる砂時計を考えてみましょう。
テストプレイの時に、3分だと長くてめんどくさいからです。w

まず、砂時計の絵をGraphicsWindowで描いてみましょう。
前回のじゃんけんの絵は適当すぎたので、今回はきちんとメモ用紙に絵を描いて計算してから書きました。
頭の中で座標の計算がぱぱっと出来るのなら構いませんが、私には無理なので……w

GraphicsWindow.Width = 800
GraphicsWindow.Height = 600
GraphicsWindow.Title = "砂時計"

'時計上下の板
GraphicsWindow.BrushColor = "SaddleBrown"
GraphicsWindow.FillRectangle(50, 10, 300, 10)
GraphicsWindow.FillRectangle(50, 580, 300, 10)

'時計左側の線
GraphicsWindow.PenColor = "DarkSlateGray"
GraphicsWindow.DrawLine(100, 20, 100, 200)
GraphicsWindow.DrawLine(100, 200, 195, 280)
GraphicsWindow.DrawLine(195, 280, 195, 320)
GraphicsWindow.DrawLine(195, 320, 100, 400)
GraphicsWindow.DrawLine(100, 400, 100, 580)

'時計右側の線
GraphicsWindow.DrawLine(300, 20, 300, 200)
GraphicsWindow.DrawLine(300, 200, 205, 280)
GraphicsWindow.DrawLine(205, 280, 205, 320)
GraphicsWindow.DrawLine(205, 320, 300, 400)
GraphicsWindow.DrawLine(300, 400, 300, 580)

tokei

計算してもこれだもんなー……w
さて、問題は砂の部分です。
タイマーで少しずつ減っていくようにしなければなりませんが、ひとまず、開始前の砂を描きましょう。
下は砂の描写のみです。

'砂
GraphicsWindow.BrushColor = "SandyBrown"
GraphicsWindow.FillRectangle(105, 100, 190, 180)
GraphicsWindow.FillRectangle(197, 280, 6, 40)
GraphicsWindow.BrushColor = "White"
GraphicsWindow.FillTriangle(105, 200, 105, 275, 195, 275)
GraphicsWindow.FillTriangle(295, 200, 295, 275, 205, 275)
GraphicsWindow.FillRectangle(105, 275, 92, 5)
GraphicsWindow.FillRectangle(203, 275, 92, 5)

tokei

砂は、長方形を描いた後に、背景色(白)の三角形や四角形で無理やり一部を消しています。
ですので、表示順序に気をつけてください。
砂を描いた後に、時計を描写する線を上に書くようにしないと、時計の一部まで白で塗りつぶされて消えてしまうからです。
砂を一番最初に描けば問題は起きないでしょう。

あとは、時間によって長方形のy座標を増加させていけば、砂が減っていくように見えるはずです。
最後の0秒で、細い管の中の砂もうまく消したいところ。

さて、タイマー部分を作ります。
カウントダウンタイマーのソースコードを流用しましょう。

GraphicsWindow.Width = 800
GraphicsWindow.Height = 600
GraphicsWindow.Title = "砂時計"

GraphicsWindow.FontName = "Wingdings 3"
GraphicsWindow.FontSize = 40
botan = Controls.AddButton("u", 700, 70)
Controls.ButtonClicked = OnClick
GraphicsWindow.FontName = "Meiryo UI"

totaltime = 60
ue_sunaY = 100 '減っていく砂の初期y座標

Sub OnClick
  Timer.Interval = 1000
  Timer.Tick = countdownevent
EndSub

Sub countdownevent
If totaltime > 0 Then
  totaltime = totaltime - 1
  ue_sunaY = ue_sunaY + 3
  '砂
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillRectangle(105, 100, 190, 180)
  GraphicsWindow.BrushColor = "SandyBrown"
  GraphicsWindow.FillRectangle(105, ue_sunaY, 190, 180 - ue_sunaY + 100) '減っていく砂、四角形の開始座標と大きさを変化させていく
  GraphicsWindow.FillRectangle(197, 280, 6, 40)
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillTriangle(105, 200, 105, 275, 195, 275)
  GraphicsWindow.FillTriangle(295, 200, 295, 275, 205, 275)
  GraphicsWindow.FillRectangle(105, 275, 92, 5)
  GraphicsWindow.FillRectangle(203, 275, 92, 5)
  '砂時計横の線。砂を毎秒描写するため、横の線も毎回書き足さないと消えてしまう
  GraphicsWindow.PenColor = "DarkSlateGray"
  GraphicsWindow.DrawLine(100, 200, 195, 280)
  GraphicsWindow.DrawLine(300, 200, 205, 280)
  'タイマー数字表示
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillRectangle(700, 200, 100, 100)
  GraphicsWindow.BrushColor = "black"
  GraphicsWindow.DrawText(700, 200, totaltime)
EndIf
If totaltime = 0 Then
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillRectangle(197, 280, 6, 40) '0秒になったら、管の中の砂を白く塗りつぶして消す
  Sound.PlayChime()
  Timer.Pause()
EndIf
EndSub

'時計上下の板
GraphicsWindow.BrushColor = "SaddleBrown"
GraphicsWindow.FillRectangle(50, 10, 300, 10)
GraphicsWindow.FillRectangle(50, 580, 300, 10)

'時計左側の線
GraphicsWindow.PenColor = "DarkSlateGray"
GraphicsWindow.DrawLine(100, 20, 100, 200)
GraphicsWindow.DrawLine(100, 200, 195, 280)
GraphicsWindow.DrawLine(195, 280, 195, 320)
GraphicsWindow.DrawLine(195, 320, 100, 400)
GraphicsWindow.DrawLine(100, 400, 100, 580)

'時計右側の線
GraphicsWindow.DrawLine(300, 20, 300, 200)
GraphicsWindow.DrawLine(300, 200, 205, 280)
GraphicsWindow.DrawLine(205, 280, 205, 320)
GraphicsWindow.DrawLine(205, 320, 300, 400)
GraphicsWindow.DrawLine(300, 400, 300, 580)

tokei

tokei

「ue_sunaY = ue_sunaY + 3」で、1秒につき砂の部分を3ピクセル減らしています。
四角形の高さを180ピクセルに決めていたので、60秒後には四角形の高さが0になるという計算です。
これを動かすと、1秒で砂が3ピクセル減ります。
もっと砂の動きを滑らかにしたいのなら、以下のようにそれぞれ変更してみましょう。

Timer.Interval = 500
totaltime = totaltime - 0.5
ue_sunaY = ue_sunaY + 1.5

これで、0.5秒につき1.5ピクセルずつ砂が減る計算です。
ただし、これだと横のカウントダウンタイマーの数字も0.5秒刻みで減りますので、Math.Ceiling(totaltime)などで、整数値を表示する必要があります。
完成品では砂時計のみ、数字は表示しない、というのなら気にしなくて良いところですが。

なお、

totaltime = 180
Timer.Interval = 1000
ue_sunaY = ue_sunaY + 1

こうすると、3分(180秒)のタイマーになります。
四角形の高さが180ピクセルなので、1秒で1ピクセル減らせば、3分(180秒)で四角形が消えるという計算です。

それはさておき、下に砂を貯めていく絵も追加しなければなりませんね。

GraphicsWindow.Width = 800
GraphicsWindow.Height = 600
GraphicsWindow.Title = "砂時計"

GraphicsWindow.FontName = "Wingdings 3"
GraphicsWindow.FontSize = 40
botan = Controls.AddButton("u", 700, 70)
Controls.ButtonClicked = OnClick
GraphicsWindow.FontName = "Meiryo UI"

totaltime = 60
ue_sunaY = 100 '減っていく砂の初期y座標
sita_suna = 575 '増えていく砂の初期y座標

Sub OnClick
  Timer.Interval = 1000
  Timer.Tick = countdownevent
EndSub

Sub countdownevent
If totaltime > 0 Then
  totaltime = totaltime - 1
  ue_sunaY = ue_sunaY + 3
  sita_suna = sita_suna - 3
  '砂
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillRectangle(105, 100, 190, 180)
  GraphicsWindow.BrushColor = "SandyBrown"
  GraphicsWindow.FillRectangle(105, ue_sunaY, 190, 180 - ue_sunaY + 100) '減っていく砂、四角形の開始座標と大きさを変化させていく
  GraphicsWindow.FillRectangle(197, 280, 6, 40)
  
  GraphicsWindow.FillRectangle(105, sita_suna, 190, -(sita_suna - 575)) '増えていく砂、四角形の開始座標と大きさを変化させていく
  
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillTriangle(105, 200, 105, 275, 195, 275)
  GraphicsWindow.FillTriangle(295, 200, 295, 275, 205, 275)
  GraphicsWindow.FillRectangle(105, 275, 92, 5)
  GraphicsWindow.FillRectangle(203, 275, 92, 5)
  
  '砂時計横の線。砂を毎秒描写するため、横の線も毎回書き足さないと消えてしまう
  GraphicsWindow.PenColor = "DarkSlateGray"
  GraphicsWindow.DrawLine(100, 200, 195, 280)
  GraphicsWindow.DrawLine(300, 200, 205, 280)
  'タイマー数字表示
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillRectangle(700, 200, 100, 100)
  GraphicsWindow.BrushColor = "black"
  GraphicsWindow.DrawText(700, 200, totaltime)
EndIf
If totaltime = 0 Then
  GraphicsWindow.BrushColor = "White"
  GraphicsWindow.FillRectangle(197, 280, 6, 40) '0秒になったら、管の中の砂を白く塗りつぶして消す
  Sound.PlayChime()
  Timer.Pause()
EndIf
EndSub

'時計上下の板
GraphicsWindow.BrushColor = "SaddleBrown"
GraphicsWindow.FillRectangle(50, 10, 300, 10)
GraphicsWindow.FillRectangle(50, 580, 300, 10)

'時計左側の線
GraphicsWindow.PenColor = "DarkSlateGray"
GraphicsWindow.DrawLine(100, 20, 100, 200)
GraphicsWindow.DrawLine(100, 200, 195, 280)
GraphicsWindow.DrawLine(195, 280, 195, 320)
GraphicsWindow.DrawLine(195, 320, 100, 400)
GraphicsWindow.DrawLine(100, 400, 100, 580)

'時計右側の線
GraphicsWindow.DrawLine(300, 20, 300, 200)
GraphicsWindow.DrawLine(300, 200, 205, 280)
GraphicsWindow.DrawLine(205, 280, 205, 320)
GraphicsWindow.DrawLine(205, 320, 300, 400)
GraphicsWindow.DrawLine(300, 400, 300, 580)


tokei

tokei

tokei

あ、砂が最後にちょっとだけはみだす……w
結局計算をミスってたようです。w
まあいいか。w
気になったら修正してください。

他に欲しいのは、「落下する砂」です。
砂粒をランダムに落とすのも試してみましたが、処理がやや重くなり、ちょっと見栄えがよくない……。
開始時に砂をざばっと落ちたように見せるだけでも、そこそこ良い感じになります。
ボタンを押した時のサブルーチンOnClickに、最初に落ちる砂(実際はShapes.AddRectangleで描いた棒ですが……)を描写させます。
Program.Delayを入れて、開始を少し遅らせると、タイマーと上手いことタイミングが合います。

Sub OnClick
  Timer.Interval = 1000
  Timer.Tick = countdownevent
  '開始時落下する砂
  Program.Delay(1300)
  GraphicsWindow.BrushColor = "SandyBrown"
  GraphicsWindow.PenColor = "SandyBrown"
  rakkasuna = Shapes.AddRectangle(6, 250)
  Shapes.Move(rakkasuna, 197, 100)
  Shapes.Animate(rakkasuna, 197, 320, 1000)
EndSub

また、上の方で書いた通り、3分(180秒)のタイマーも可能なので、1分タイマーと3分タイマーのボタンを設置し、どちらが押されたかでタイマーの時間を変更させてみましょう。
また、1分のタイマーと3分のタイマーを同時に動かすと砂時計の描写がめちゃくちゃになるので、片方のタイマーを動かしている最中は、もう片方のタイマーのボタンを消す処理も入れましょう。

GraphicsWindow.Width = 800
GraphicsWindow.Height = 600
GraphicsWindow.Title = "砂時計"

GraphicsWindow.FontName = "Wingdings 3"
GraphicsWindow.FontSize = 40
botan1 = Controls.AddButton("u", 700, 70)
botan3 = Controls.AddButton("u", 700, 150)
GraphicsWindow.FontName = "Meiryo UI"
GraphicsWindow.DrawText(600, 70, "1分")
GraphicsWindow.DrawText(600, 150, "3分")
Controls.ButtonClicked = OnClick

ue_sunaY = 100 '減っていく砂の初期y座標
sita_suna = 575 '増えていく砂の初期y座標

Sub OnClick
  Timer.Interval = 1000
  Timer.Tick = countdownevent
  If Controls.LastClickedButton = botan1 Then
    totaltime = 60
  EndIf
  If Controls.LastClickedButton = botan3 Then
    totaltime = 180
  EndIf
  '開始時落下する砂
  Program.Delay(1300)
  GraphicsWindow.BrushColor = "SandyBrown"
  GraphicsWindow.PenColor = "SandyBrown"
  rakkasuna = Shapes.AddRectangle(6, 250)
  Shapes.Move(rakkasuna, 197, 100)
  Shapes.Animate(rakkasuna, 197, 320, 1000)
EndSub

Sub countdownevent
  If Controls.LastClickedButton = botan1 And totaltime > 0 Then
    totaltime = totaltime - 1
    ue_sunaY = ue_sunaY + 3
    sita_suna = sita_suna - 3
    Controls.HideControl(botan3)
  EndIf
  If Controls.LastClickedButton = botan3 And totaltime > 0 Then
    totaltime = totaltime - 1
    ue_sunaY = ue_sunaY + 1
    sita_suna = sita_suna - 1
    Controls.HideControl(botan1)
  EndIf
  If totaltime > 0 Then
    GraphicsWindow.BrushColor = "White"
    GraphicsWindow.FillRectangle(105, 100, 190, 180)
    GraphicsWindow.BrushColor = "SandyBrown"
    GraphicsWindow.FillRectangle(105, ue_sunaY, 190, 180 - ue_sunaY + 100) '減っていく砂、四角形の開始座標と大きさを変化させていく
    GraphicsWindow.FillRectangle(197, 280, 6, 40)

    GraphicsWindow.FillRectangle(105, sita_suna, 190, -(sita_suna - 575)) '増えていく砂、四角形の開始座標と大きさを変化させていく
  
    GraphicsWindow.BrushColor = "White"
    GraphicsWindow.FillTriangle(105, 200, 105, 275, 195, 275)
    GraphicsWindow.FillTriangle(295, 200, 295, 275, 205, 275)
    GraphicsWindow.FillRectangle(105, 275, 92, 5)
    GraphicsWindow.FillRectangle(203, 275, 92, 5)
  
    '砂時計横の線。砂を毎秒描写するため、横の線も毎回書き足さないと消えてしまう
    GraphicsWindow.PenColor = "DarkSlateGray"
    GraphicsWindow.DrawLine(100, 200, 195, 280)
    GraphicsWindow.DrawLine(300, 200, 205, 280)
  
    'タイマー数字表示
    GraphicsWindow.BrushColor = "White"
    GraphicsWindow.FillRectangle(600, 300, 100, 100)
    GraphicsWindow.BrushColor = "black"
    GraphicsWindow.DrawText(600, 300, totaltime)
  EndIf
  If totaltime = 0 Then
    GraphicsWindow.BrushColor = "White"
    GraphicsWindow.FillRectangle(197, 280, 6, 40) '0秒になったら、管の中の砂を白く塗りつぶして消す
    Shapes.Remove(rakkasuna)
    Sound.PlayChime()
    Timer.Pause()
    Controls.ShowControl(botan1)
    Controls.ShowControl(botan3)
  EndIf
EndSub

'時計上下の板
GraphicsWindow.BrushColor = "SaddleBrown"
GraphicsWindow.FillRectangle(50, 10, 300, 10)
GraphicsWindow.FillRectangle(50, 580, 300, 10)

'時計左側の線
GraphicsWindow.PenColor = "DarkSlateGray"
GraphicsWindow.DrawLine(100, 20, 100, 200)
GraphicsWindow.DrawLine(100, 200, 195, 280)
GraphicsWindow.DrawLine(195, 280, 195, 320)
GraphicsWindow.DrawLine(195, 320, 100, 400)
GraphicsWindow.DrawLine(100, 400, 100, 580)

'時計右側の線
GraphicsWindow.DrawLine(300, 20, 300, 200)
GraphicsWindow.DrawLine(300, 200, 205, 280)
GraphicsWindow.DrawLine(205, 280, 205, 320)
GraphicsWindow.DrawLine(205, 320, 300, 400)
GraphicsWindow.DrawLine(300, 400, 300, 580)


tokei

これで動きはしますが、3分だと最初の落下する棒の長さが足りなかったりします……w
そこらへんは、砂時計の大きさ設計からやり直さないと上手くできそうにありませんね。
ということで、最初のサイズ設計が如何に重要かがわかります。w



▲TOPへ戻る