前回、暇つぶしという名のアプリを作りましたが、今回は動く暇をクリックしてつぶしていくアプリケーションをつくります。本日は完全にネタ記事です。
MovingRectangleクラスの生成
前回作成したものは一度表示された暇はクリックされるまで出現した位置にとどまり続けます。そのため位置を管理するためにPointのリストに格納していくだけでよかったのですが、今回は移動するので変わりつつある表示座標を保持するクラスを作成します。
ではそのクラスを示します。移動する矩形なのでクラス名はMovingRectangleにしました。
フィールド変数として現在位置、移動量、サイズ、フォント、ブラシがあります。フォントは同じものを使用するので静的変数にしています。
どうせやるなら色も文字ごとに変えてしまおうということでコンストラクタで指定できるようにしています。コンストラクタは出現地点、移動先、サイズ、色です。コンストラクタ内で必要なデータをフィールド変数に格納して移動元と移動先の座標から移動方向を計算してX方向、Y方向の移動量を求めています。
使わなくなったらブラシをDisposeしています。IsHitメソッドは引数としてわたされたPointは矩形のなかにあるかどうかを調べるものです。クリックされた座標をこのメソッドに渡してtrueを返せば、移動している暇をつぶすことができたということになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
public class MovingRectangle { // 現在座標 double X = 0; double Y = 0; // 移動量 double VX = 0; double VY = 0; Size Size = Size.Empty; Color Color = Color.Empty; SolidBrush SolidBrush = null; static Font Font = new Font("MS ゴシック", 20); public MovingRectangle(Point point, Point targetPoint, Size size, Color color) { X = point.X; Y = point.Y; Size = size; Color = color; SolidBrush = new SolidBrush(Color); double angle = Math.Atan2(targetPoint.Y - Y, targetPoint.X - X); VX = Math.Cos(angle) * 1.5; VY = Math.Sin(angle) * 1.5; } // IsDead = trueにしたらSolidBrushはDisposeする bool _isDead = false; public bool IsDead { get { return _isDead; } set { _isDead = value; if (_isDead) { SolidBrush.Dispose(); } } } // 移動量だけ移動させる public void Move() { X += VX; Y += VY; } // IsDead = trueでないなら描画する public void Draw(Graphics graphics) { if (!IsDead) graphics.DrawString("暇", Font, SolidBrush, (int)X, (int)Y); } // ptは矩形の内部か? public bool IsHit(Point pt) { Rectangle rect = new Rectangle((int)X, (int)Y, Size.Width, Size.Height); if (rect.Left < pt.X && pt.X < rect.Right && rect.Top < pt.Y && pt.Y < rect.Bottom) return true; else return false; } } |
Form1クラス
次にForm1クラスをどうするかですが、文字を動かすためにタイマー処理をおこなうのでTimerが必要です。TickCountはTimer.Tickイベントが発生した回数です。暇の出現位置をランダムにしたいのでRandomクラスも使用します。MovingRectanglesはMovingRectangleクラスのインスタンスを格納するリストです。暇をどれだけ潰せたか打率(?)のようなものも表示させたいのでこれまでに出現した暇とつぶすことができた暇の数もフィールド変数で管理します。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class Form1 : Form { Timer Timer = new Timer(); int TickCount = 0; Random Random = new Random(); List<MovingRectangle> MovingRectangles = new List<MovingRectangle>(); int HimaCount = 0; int DestroyedHimaCount = 0; } |
Form1クラスのコンストラクタを示します。タイマーを初期化して背景を黒にして(とくに意味はない)、暇の色を先に作成してリストに格納しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public partial class Form1 : Form { List<Color> Colors = new List<Color>(); public Form1() { InitializeComponent(); Timer.Interval = 1000 / 60; Timer.Tick += Timer_Tick; Timer.Start(); DoubleBuffered = true; BackColor = Color.Black; Colors.Add(Color.FromArgb(0xff, 0x00, 0x00)); Colors.Add(Color.FromArgb(0xff, 0xff, 0x00)); Colors.Add(Color.FromArgb(0x00, 0xff, 0x00)); Colors.Add(Color.FromArgb(0x00, 0xff, 0xff)); Colors.Add(Color.FromArgb(0x00, 0x00, 0xff)); Colors.Add(Color.FromArgb(0xff, 0x00, 0xff)); } } |
フォームが表示されたら暇がどのように描画されるか実際に描画してみます。これで矩形がどうなっているかを知ることができます。文字の幅、高さをstringWidthとstringHeightに格納しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { string str = "暇"; Font strFont = new Font("MS ゴシック", 20); int stringWidth = 0; int stringHeight = 0; protected override void OnLoad(EventArgs e) { Point pt = new Point(10, 10); Graphics graphics = Graphics.FromHwnd(this.Handle); TextRenderer.DrawText(graphics, str, strFont, pt, Color.Black, TextFormatFlags.NoPadding); // 大きさを計測して、四角を描画する Size nopadSize = TextRenderer.MeasureText(graphics, str, strFont, new Size(this.ClientSize.Width, this.ClientSize.Height), TextFormatFlags.NoPadding); graphics.Dispose(); stringWidth = nopadSize.Width; stringHeight = nopadSize.Height; } } |
Timer.Tickイベント時の処理を示します。TickCountがインクリメントされていくので、これが100で割ったときの剰余が0, 25, 50, 75のときに暇を出現させます。出現場所はフォームの上下左右の端です。出現位置の向かいの辺のどこかへ向かって移動していきます。このあたりは暇の色も含めて乱数をつかって適当にバラけるようにしています。
暇が生成されたらHimaCountをインクリメントします。そしてMovingRectanglesのなかからMovingRectangle.IsDead = falseのものだけ集めて移動させます。最後に再描画をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
public partial class Form1 : Form { private void Timer_Tick(object sender, EventArgs e) { if (TickCount % 100 == 0) { int r1 = Random.Next(ClientSize.Height); int r2 = Random.Next(ClientSize.Height); int r3 = Random.Next(Colors.Count); MovingRectangles.Add(new MovingRectangle(new Point(0, r1), new Point(ClientSize.Width, r2), new Size(stringWidth, stringHeight), Colors[r3])); HimaCount++; } if (TickCount % 100 == 25) { int r1 = Random.Next(ClientSize.Height); int r2 = Random.Next(ClientSize.Height); int r3 = Random.Next(Colors.Count); MovingRectangles.Add(new MovingRectangle(new Point(ClientSize.Width, r1), new Point(0, r2), new Size(stringWidth, stringHeight), Colors[r3])); HimaCount++; } if (TickCount % 100 == 50) { int r1 = Random.Next(ClientSize.Height); int r2 = Random.Next(ClientSize.Height); int r3 = Random.Next(Colors.Count); MovingRectangles.Add(new MovingRectangle(new Point(r1, 0), new Point(r2, ClientSize.Height), new Size(stringWidth, stringHeight), Colors[r3])); HimaCount++; } if (TickCount % 100 == 75) { int r1 = Random.Next(ClientSize.Height); int r2 = Random.Next(ClientSize.Height); int r3 = Random.Next(Colors.Count); MovingRectangles.Add(new MovingRectangle(new Point(r1, ClientSize.Height), new Point(r2, 0), new Size(stringWidth, stringHeight), Colors[r3])); HimaCount++; } MovingRectangles = MovingRectangles.Where(x => !x.IsDead).ToList(); foreach (MovingRectangle rect in MovingRectangles) rect.Move(); TickCount++; this.Invalidate(); } } |
クリックされたときの処理を示します。MovingRectangle.IsHitメソッドで暇のなかでクリックされたのかどうかがわかるので、もし該当する暇があったらこれをIsDead = trueにします。そしてShotCountとDestroyedHimaCountをインクリメントします。これで総クリック数に対する暇つぶし成功率がわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { int ShotCount = 0; protected override void OnMouseDown(MouseEventArgs e) { ShotCount++; MovingRectangle rect = MovingRectangles.FirstOrDefault(x => x.IsHit(new Point(e.X, e.Y))); if (rect != null) { rect.IsDead = true; DestroyedHimaCount++; } } } |
あとはOnPaintメソッドをオーバーライドして再描画時の処理をおこないます。つぶされずに残っている暇を表示させるとともに暇つぶしの成績を表示させます。これまで出現した暇に対するつぶせた割合、総クリックに対する暇つぶし成功の割合を表示させます。打率が60%を下回ったときは「ミスショットが増えています。もっと落ち着きましょう。」と赤で表示させます(ちょっとしたユーモアということで・・・)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public partial class Form1 : Form { protected override void OnPaint(PaintEventArgs e) { foreach (MovingRectangle rect in MovingRectangles) rect.Draw(e.Graphics); if (HimaCount > 0) { double a = Math.Ceiling(1000d * DestroyedHimaCount / HimaCount) / 10; string str = String.Format("{0}個の暇をつぶしました。あなたの暇つぶし率は{1}%です。", DestroyedHimaCount, a); e.Graphics.DrawString(str, new Font("MS ゴシック", 12), new SolidBrush(Color.White), new Point(10, 10)); a = Math.Ceiling(1000d * DestroyedHimaCount / ShotCount) / 10; str = String.Format("暇つぶし成功率は{0}%です。", a); e.Graphics.DrawString(str, new Font("MS ゴシック", 12), new SolidBrush(Color.White), new Point(10, 40)); if (a < 60) { str = String.Format("ミスショットが増えています。もっと落ち着きましょう。"); e.Graphics.DrawString(str, new Font("MS ゴシック", 12, FontStyle.Bold), new SolidBrush(Color.Red), new Point(10, 70)); } } base.OnPaint(e); } } |