前回:Visual StudioとC#を初めて使ってブロック崩しゲームを作ってみた②
前回の続きです。
リザルト画面を作っていきます。
リザルト画面のアウトライン
・リプレイボタン
・終了ボタン
・成績表示
考えるところは成績くらいかな
成績のディティール
・ブロック数とモードに応じてSCORE表示
・経過時間を表示
・選択モード表示
こんな感じで進めていこうと思います。
(補足)
ホーム画面:Form2
プレイ画面:Form1
リザルト画面:Form3
画面追加
Form2にプレイ画面とリザルト画面を表示したかったのですが、どうやらC#ではできないようです。。
ボタンやテキストボックスなどのコントロールを切替る方法はありました。
ウィンドウ内遷移
パネルとグループボックス
ブロックはプログラムで作成しているので、大人しく別ウィンドウで作成します。
前回同様画面とボタンを作っていきます。
追加の配下にWindowsフォームがあったので、今回はここから作成します。
すると自動でWindowsフォームが選択され、ファイル名もデフォルトの「Form3.cs」で追加します。
ここも前回同様にボタンを追加
テキストは以下の通りにしてください。
リプレイのクリックイベントは
「replay_Click」、
終了のクリックイベントは
「backHome_Click」とします。
ボタン処理を記述したリザルト画面コードは以下です。
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows.Forms;namespaceBreakout{publicpartialclassForm3:Form{publicForm3(){InitializeComponent();}privatevoidreplay_Click(objectsender,EventArgse){//画面を閉じ、プレイ画面を開くthis.Close();this.Hide();Form1form1=newForm1();form1.ShowDialog();}privatevoidbackHome_Click(objectsender,EventArgse){//画面を閉じるthis.Close();}}}
Close()で画面は消せますが、その後すぐにプレイ画面を呼んでいる為
画面は開いたままになってしまいます。Close処理が流れた瞬間に画面が閉じるわけではないからです。
そこでHide()で非表示にします。
(Application.Exit();
でアプリ自体を終了できる)
プレイ画面に失敗時の処理を追加します。
まずはタイマーのインスタンス作成部分をクラス直下へ移動します。下記1行。
わかりにくかったら「Ctrl + F」でtimer等検索してください。
これでタイマーインスタンスのスコープを広げ
他のメソッドでも呼び出せるようにします。
Timertimer=newTimer();
Updateメソッドに以下を追加。
//失敗時if(ballPos.Y>paddlePos.Y){//画面閉じてリザルト表示timer.Stop();this.Close();this.Hide();Form3form3=newForm3();form3.ShowDialog();}
リザルト画面のClose処理と違うのはtimer.Stop
があることですね。
さっきのタイマーインスタンスの移動はこの為です。
タイマーを終了させないと無限にリザルト画面が呼び出されてしまいます(すぐ止まりますが)。
これでボールがパドルより下に来ると終了してリザルト画面が呼ばれるはずです。
実行してみてください。
成績表示
まずブロックの数を数えます。
blockNumで終了時のブロックの数をカウントします。
追加する箇所は、変数blockNumの定義とブロック作成時に値+1、ブロック削除時に-1。
blockNumと検索してみてください。5か所見えるはずです。
blockNumMaxで最大値を求めます。2か所見えます。
変更のないメソッドは省略。
namespaceBreakout{publicpartialclassForm1:Form{VectorballPos;//位置(Vector:2D空間における変位を表す)VectorballSpeed;intballRadius;//半径RectanglepaddlePos;//パドル位置(Rectangle:四角形を作成)List<Rectangle>blockPos;//ブロックの位置(リスト化)Timertimer=newTimer();publicstaticintblockNum{get;set;}// ブロック数publicstaticintblockNumMax{get;set;}// ブロック数最大値publicForm1(){InitializeComponent();//設定したハンドラ等の初期設定this.ballSpeed=newVector(Form2.x,Form2.y);//Form2で設定した値を代入this.ballPos=newVector(200,200);this.ballRadius=10;this.paddlePos=newRectangle(100,this.Height-50,100,5);//(位置横縦,サイズ横縦)this.blockPos=newList<Rectangle>();for(intx=0;x<=this.Height;x+=100){for(inty=0;y<=150;y+=40){this.blockPos.Add(newRectangle(25+x,y,80,25));blockNum++;}}blockNumMax=blockNum;//タイマーtimer.Interval=33;timer.Tick+=newEventHandler(Update);//timer.Trik:Timer有効時に呼ばれるtimer.Start();}privatevoidUpdate(objectsender,EventArgse){//ボールの移動ballPos+=ballSpeed;//左右の壁でのバウンドif(ballPos.X+ballRadius*2>this.Bounds.Width||ballPos.X-ballRadius<0){ballSpeed.X*=-1;}//上の壁でバウンドif(ballPos.Y-ballRadius<0){ballSpeed.Y*=-1;}//パドルの当たり判定if(LineVsCircle(newVector(this.paddlePos.Left,this.paddlePos.Top),newVector(this.paddlePos.Right,this.paddlePos.Top),ballPos,ballRadius)){ballSpeed.Y*=-1;}// ブロックとのあたり判定for(inti=0;i<this.blockPos.Count;i++){intcollision=BlockVsCircle(blockPos[i],ballPos);if(collision==1||collision==2){ballSpeed.Y*=-1;this.blockPos.Remove(blockPos[i]);blockNum--;}elseif(collision==3||collision==4){ballSpeed.X*=-1;this.blockPos.Remove(blockPos[i]);blockNum--;}}//失敗時if(ballPos.Y>paddlePos.Y){//画面閉じてリザルト表示timer.Stop();this.Close();this.Hide();Form3form3=newForm3();form3.ShowDialog();}//画面再描画Invalidate();}}}
文字サイズはプロパティのFontをクリックすれば「・・・」が表示されるので、
そこから変更できる。
モードの値を受け渡す為にホーム画面に変数modeを追加。
2行追加。変更のないメソッドは省略。
namespaceBreakout{publicpartialclassForm2:Form{//ボールの速度(x, y)publicstaticintx{get;set;}publicstaticinty{get;set;}//モードpublicstaticintmode{get;set;}privatevoidmode_Select(objectsender,EventArgse)//モードセレクト{intselectedMode=comboBox1.SelectedIndex;mode=selectedMode;switch(selectedMode){case0://Easyx=-2;y=-4;break;case1://Normalx=-3;y=-6;break;case2://Hardx=-5;y=-10;break;case3://Expertx=-8;y=-16;break;default://未選択時はNormalx=-3;y=-6;break;}}}}
スコアを表示していきます。
Form3のウィンドウを選択してプロパティを開き、
from3_loadメソッドを作成します。
Form3(リザルト画面)は以下になります。
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows.Forms;namespaceBreakout{publicpartialclassForm3:Form{publicForm3(){InitializeComponent();}privatedoublescore(){//倍率設定doublebairitsu=((Form2.mode+1)/10*2)+1;//基準点300*ブロック数*モード倍率return300*(Form1.blockNumMax-Form1.blockNum)*bairitsu;}privatevoidreplay_Click(objectsender,EventArgse){//画面を閉じ、プレイ画面を開くthis.Close();this.Hide();Form1form1=newForm1();form1.ShowDialog();}privatevoidbackHome_Click(objectsender,EventArgse){//画面を閉じるthis.Close();}privatevoidlabel2_Click(objectsender,EventArgse){}privatevoidform3_load(objectsender,EventArgse){label2.Text=score().ToString();}}}
倍率をbairitsu = ((Form2.mode + 1) /10 * 2) + 1
としていますが
ややこしかったらbairitsu = Form2.mode + 1
だけでもいいです。
これだとモード:Easy(要素0)で倍率1です。
ラベルはlabel2.Text
で変更でき、
受け取ったスコアを文字に変換していますscore().ToString()
。
修正
実行してみてください。
きちんと表示されましたか?
スコア数が桁数によって左右にずれるのが気になったので、
常時センター表示に変更します。
桁数少ないときに左端に寄ってしまうので、AutoSizeもfalseへ変更。
EasyからExpertまで試してみたのですが、Expertのときだけボールがパネルに触れる前に終了してしまいます。
遷移時の条件はコレです。ballPos.Y > paddlePos.Y
コンソールに出力します。
Console.WriteLine(ballPos.Y+":"+paddlePos.Y);//失敗時if(ballPos.Y>paddlePos.Y){//画面閉じてリザルト表示timer.Stop();this.Close();this.Hide();Form3form3=newForm3();form3.ShowDialog();}
もう一度Expertで同じ動作をします。
その後、アプリを終了させると出力結果が見れます。
一瞬423を超えていますね。
ボールの位置は→ballPos += ballSpeed;
Expert時のY軸移動数が16
出力を見ると408の次に16足された424が来てしまってますね。
なので、余裕を持って遷移条件をボールが画面から消えた時にします。
遷移時の条件を以下へ変更ballPos.Y > paddlePos.Y → ballPos.Y > this.Height
プロパティのSizeを見ると画面のY軸473と余裕ありますね!(Size数はコレに合わせる必要はない)
これで不具合はなくなりました。
にしても、パドルの移動距離が少ないからExpertが難しい・・・
機能アップデートは第4回で実施します。しばしお待ちを。
経過時間表示
Stopwatchクラスを利用します。
Form1で3か所に1行ずつ追加します。
publicpartialclassForm1:Form{//追記①publicstaticStopwatchkeikaTime=newStopwatch();//経過時間・・・publicForm1(){//追記②keikaTime.Restart();//経過時間スタート・・・//失敗時if(ballPos.Y>this.Height){//追記③keikaTime.Stop();//停止・・・
Startではなく、Restartを使っているのは
リプレイしたときにStartだとリセットされないからです。
Restartだとリセット+スタートの機能を持ちます。
Form3で経過時間を受け取ります。
form3_loadメソッドで以下1行を追加してください。
privatevoidform3_load(objectsender,EventArgse){//経過時間例 00:01:03.1235785 → 03.123label4.Text=Form1.keikaTime.Elapsed.ToString().Substring(6,6);}
Stopwatchの値を「Elapsed」で取得すると「00:01:03.1235785」のような表記になります。
スコアと同じように「ToString」で文字列にし、
「Substring(6,6)」で6桁目から6桁を切り取ります(要素と同じで最初を0桁目とし数えます)。
選択モード表示
もうここまで来れば説明しなくてもできそうですがw
選択モードのリザルト表示も解説します。
まずはラベルを用意します。
せっかくなのでタイムとモードは英文字で統一しました。
未選択時はNormal なのでテキストは「Normal 」。
Form2.csで渡す変数を定義。
//モードテキストpublicstaticstringmodeText{get;set;}
mode_Selectメソッドに以下を追加。
if(comboBox1.SelectedItem==null){modeText="Normal";}else{modeText=comboBox1.SelectedItem.ToString();}
Form3.csのform3_loadメソッドで受け取り代入。
label6.Text=Form2.modeText;
実行してみてください。
未選択にNormal 、選択時に選択したモードが表示されていれば成功!
修正
プレイ画面を×ボタンで閉じても、
裏でボールは動き続けリザルトへ遷移してしまう。
Form1のプロパティのイベントでFormClosingを設定します。
これは×ボタン押下時に処理が流れます。
失敗時の処理をコピペして終わり。
privatevoidform1_Closing(objectsender,FormClosingEventArgse)//×ボタン押下時{keikaTime.Stop();timer.Stop();this.Close();this.Hide();}
お疲れ様でした。
感想
今回は一時パドルの挙動がおかしくなって時間かかってしまいました。
そんなときは一度Visual Studioを再起動すると直ったり!・・・
全体ソースを載せていないのでGitHubに投稿しました。
以下から確認できます。気になるCSファイル等を見てください。
https://github.com/seyryo/sayryo/tree/start/Breakout
次回は
カウントダウンスタートとCLEAR時の処理を作っていく予定です。↓作成中