イベントハンドラの作成
以下の機能を実装します。
Form1のコンストラクタ(生成時)の処理Form1のLoadイベント(初期表示時)の処理backgroundWorker1のDoWorkイベント(別スレッドでのサーバ開始)の処理timer1のTickイベント(タイマによる指定間隔ごと)の処理pictureBox1のPaint(画面描画)の処理pictureBox1のMouseMove(マウス移動)の処理button1のClickイベント([Restart]ボタンを押したとき)の処理
Form1のコンストラクタ(生成時)の処理
ソリューションエクスプローラで「Form1.cs」を右クリックして、[コードの表示]を行ってください。Form1クラスを次のように修正します。
public partial class Form1 : Form { private const int portno = 8102; private const int mgn = 50; internal const float barRate = 0.2F; internal const float boardRate = 0.3F; private Server svr; private bool bFirst = true; public Form1() { InitializeComponent(); SetStyle(ControlStyles.ResizeRedraw, true); } }
portnoは、TCP/IPで利用するポート番号です。もし、後述の実行がうまくいかない場合、適当な違う値に変更してみてください。
Form1のLoadイベント(初期表示時)の処理
デザイン画面でForm1のタイトルをダブルクリックしてください。開いたコード画面のイベントハンドラ(Form1_Load)を次のように修正してください。
private void Form1_Load(object sender, EventArgs e) { try { System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel( new System.Runtime.Remoting.Channels.Tcp.TcpChannel(portno), false); backgroundWorker1.RunWorkerAsync(); System.Threading.Thread.Sleep(100); } catch { bFirst = false; } svr = (Server)Activator.GetObject(typeof(Server), "tcp://" + Properties.Settings.Default.Server + ":" + portno + "/TennisGame"); if (!bFirst) svr.IsWaiting = false; }
tryの中では、サーバオブジェクトの登録とサーバの起動を行っています。既にサーバが起動されていれば、例外が発生し、bFirst = falseとなります。「svr =」では、クライアント側で、サーバオブジェクトのプロクシを生成しています。プロクシの見た目はServer型ですが、実際は異なります。ここの処理には、サーバ側の処理とクライアント側の処理が含まれますが、通常は、サーバ処理とクライアント処理は別のアプリケーションで作成されるべきです。
backgroundWorker1のDoWorkイベント(別スレッドでのサーバ開始)の処理
デザイン画面でbackgroundWorker1をダブルクリックしてください。開いたコード画面のイベントハンドラ(backgroundWorker1_DoWork)を次のように修正してください。
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { System.Runtime.Remoting.RemotingConfiguration .RegisterWellKnownServiceType( typeof(Server), "TennisGame", System.Runtime.Remoting.WellKnownObjectMode.Singleton); }
BackgroundWorkerにより別スレッドで、サーバの登録処理を行います。ここでは、ボールの位置などの情報を共有するために、シングルトンとしています。
timer1のTickイベント(タイマによる指定間隔ごと)の処理
デザイン画面でtimer1をダブルクリックしてください。開いたコード画面のイベントハンドラ(timer1_Tick)を次のように修正してください。
private void timer1_Tick(object sender, EventArgs e) { timer1.Stop(); try { if (!(button1.Visible = !svr.IsRunning) && bFirst) svr.Do(); pictureBox1.Refresh(); timer1.Start(); } catch { } }
サーバでボールの位置を移動するメソッドDoを呼んで再描画しています。本アプリケーションでは、ボールの位置などのデータはサーバで管理し、クライアントから定期的にその値を取得して表示するようになっています。呼び出し関係が、一方向になり、プログラムの構造が簡潔になります。
pictureBox1のPaint(画面描画)の処理
デザイン画面でpictureBox1を選択してください。
プロパティ画面のカミナリマークをクリックして、Paintプロパティをダブルクリックしてください。
開いたコード画面のイベントハンドラ(pictureBox1_Paint)を次のように修正してください。
private void pictureBox1_Paint(object sender, PaintEventArgs e) { try { Graphics g = e.Graphics; int w = pictureBox1.Width - 2 * mgn, h = pictureBox1.Height - 2 * mgn; float x = svr.DiscPos.X, y = bFirst ? svr.DiscPos.Y : 1 - svr.DiscPos.Y, r = boardRate, tx = mgn + r / 2 * w; float dx = (1 - r * y) * Server.Size * w; float dy = (1 - r * y) * (1 - r * y) * Server.Size * h; float z = (1 - (1 - r * y) * (1 - r * y)) / r / (2 - r); float cx = mgn + x * w - r * z * w * (x - 0.5F); float cy = pictureBox1.Height - mgn - h * z; float x1 = mgn + svr.BarPos(bFirst) * w; float x2 = mgn + svr.BarPos(!bFirst) * (1 - r) * w + w * r / 2; float bar1 = w * barRate; float bar2 = w * barRate * (1 - r); g.FillPolygon(Brushes.Green, new PointF[] { new PointF(tx, mgn), new PointF(pictureBox1.Width - tx, mgn), new PointF(pictureBox1.Width - mgn, pictureBox1.Height - mgn), new PointF(mgn, pictureBox1.Height - mgn), }); g.DrawLine(new Pen(bFirst ? Color.Red : Color.Yellow, 6), x1, pictureBox1.Height - mgn, x1 + bar1, pictureBox1.Height - mgn); g.DrawLine(new Pen(bFirst ? Color.Yellow : Color.Red, 3), x2, mgn, x2 + bar2, mgn); g.FillEllipse(Brushes.White, cx - dx, cy - dy, dx * 2, dy * 2); g.DrawEllipse(Pens.Black, cx - dx, cy - dy, dx * 2, dy * 2); Font fnt = new Font(Font.FontFamily, 16); g.DrawString(string.Format("({0}-{1})", svr.Score(bFirst), svr.Score(!bFirst)), fnt, Brushes.LightBlue, 10, 10); if (!svr.IsRunning) g.DrawString(svr.Score(bFirst) == Server.WinPoint ? "You win !" : "You loss", fnt, Brushes.LightBlue, Width / 2 - 40, Height / 2); } catch { } }
ここでは、ボールやバーなどを3次元的に描画しています。ボールの位置やバーの位置は、サーバで管理しています。従って、別のPCであっても(上下は逆になりますが)同じ情報を表示することができます。
pictureBox1のMouseMove(マウス移動)の処理
デザイン画面でpictureBox1を選択してください。
プロパティ画面のカミナリマークをクリックして、MouseMoveプロパティをダブルクリックしてください。
開いたコード画面のイベントハンドラ(pictureBox1_MouseMove)を次のように修正してください。
private void pictureBox1_MouseMove(object sender, MouseEventArgs e) { if (Math.Abs(e.Y - pictureBox1.Height + mgn) > 30) return; float x = e.X - mgn - (svr.BarPos(bFirst) + barRate / 2) * (pictureBox1.Width - 2 * mgn); if (x < -10) svr.Move(bFirst, true); else if (x > 10) svr.Move(bFirst, false); }
マウス操作で、バーを移動できるようにしています。
button1のClickイベント(「Restart」ボタンを押したとき)の処理
デザイン画面でbutton1をダブルクリックしてください。開いたコード画面のイベントハンドラ(button1_Click)を次のように修正してください。
private void button1_Click(object sender, EventArgs e) { try { svr.Reset(); button1.Visible = false; } catch { } }
ゲームの再実行ができます。
実行
- [ビルド]メニューの[ソリューションのビルド]を行います。
- 実行してみましょう。[デバッグ]メニューの[デバッグなしで開始]を選んで実行してください。
- 画面を動かして、もう1つ起動させましょう。ゲームが開始されます。マウスで操作してください。3点先取すると勝ちです。
まとめ
対戦型テニスゲームの作成を通して、.NET Remotingの利用方法を見ました。
