少しばかり自由時間を与えられたので勉強がてらにゲームアプリを探していたところ丁度良いものを発見!
文法の基礎さえ知っていれば作れるレベル。
ベクトルが登場するので、高校数学苦手なら少し難しいかも・・・
ボール作成
参考その1
残念ながらオリジナルではないです。
上のサイトを見ながら作りました。
Visual Studioのインストール手順も載っています。
その1.ではボールが移動し跳ね返るところまで。
基本は参考サイトを見ていただいて
初学者にわかりやすくする為、こちらでさらにコメント追記や軽い解説をしていく。
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Forms;namespaceBreakout{publicpartialclassForm1:Form{VectorballPos;//位置(Vector:2D空間における変位を表す)VectorballSpeed;intballRadius;//半径publicForm1(){InitializeComponent();//設定したDraw等のイベントハンドラを呼ぶthis.ballPos=newVector(200,200);this.ballSpeed=newVector(-3,-4);this.ballRadius=10;Timertimer=newTimer();timer.Interval=33;timer.Tick+=newEventHandler(Update);//timer.Trik:Timer有効時に呼ばれるtimer.Start();}privatevoidUpdate(objectsender,EventArgse){//ボールの移動ballPos+=ballSpeed;//左右の壁でのバウンドif(ballPos.X+ballRadius>this.Bounds.Width||ballPos.X-ballRadius<0){ballSpeed.X*=-1;}//上の壁でバウンドif(ballPos.Y-ballRadius<0){ballSpeed.Y*=-1;}//画面再描画Invalidate();}privatevoidDraw(objectsender,PaintEventArgse)//Draw意味:描画する{SolidBrushpinkBrush=newSolidBrush(Color.HotPink);//SolidBrush(ブラシ)は.Netのクラスで図形を塗り潰す//左上からの位置を設定floatpx=(float)this.ballPos.X-ballRadius;//マイナス半径とすることで円の中心になるfloatpy=(float)this.ballPos.Y-ballRadius;//e.描画.円(色, 横, 縦, 物質幅, 物質高さ)e.Graphics.FillEllipse(pinkBrush,px,py,this.ballRadius*2,this.ballRadius*2);}}}
解説
上のメソッド毎に渡される「object sender」「e」は
senterでどの部品か判定し、eにその情報を含んでいる。
このようにイベントで動くメソッドをイベントハンドラという。
↓メリットの例としては呼ばれた時点で部品を判断でき、
まとめて管理できる。
privatevoidButton_Click(objectSender,EventArgse){switch((SenderasButton).Name){case"Button1":// Button1 のクリック処理break;case"Button2":// Button2 のクリック処理break;case"Button3":// Button3 のクリック処理break;...}}
修正箇所
左右壁に触れるときボールが半分壁にめり込むのが気になったので、
判定条件を少し変更している。//左右の壁でのバウンド
if (ballPos.X + ballRadius * 2 > this.Bounds.Width || ballPos.X - ballRadius < 0)
パドル作成
参照その2
ここに来て高校数学で習ったベクトルが登場する...
全く覚えていない。。
鋭角三角形のcosθがマイナスになることはggって思い出し、
ベクトルの定理は呪文として理解した(理解したとは言っていない)。
当たり判定はパドルとボールのX軸Y軸座標から求めることもできるが、
後で作成するブロックにも同じ処理を使えるよう
汎用性のある法線の長さから求めるべきだろう。
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Forms;namespaceBreakout{publicpartialclassForm1:Form{VectorballPos;//位置(Vector:2D空間における変位を表す)VectorballSpeed;intballRadius;//半径RectanglepaddlePos;//パドル位置(Rectangle:四角形を作成)publicForm1(){InitializeComponent();//設定したハンドラ等の初期設定this.ballPos=newVector(200,200);this.ballSpeed=newVector(-3,-4);this.ballRadius=10;this.paddlePos=newRectangle(100,this.Height-50,100,5);//(位置横縦,サイズ横縦)Timertimer=newTimer();timer.Interval=33;timer.Tick+=newEventHandler(Update);//timer.Trik:Timer有効時に呼ばれるtimer.Start();}/// <summary>/// 内積計算/// </summary>/// <param name="a"></param>/// <param name="b"></param>/// <returns></returns>doubleDotProduct(Vectora,Vectorb){returna.X*b.X+a.Y*b.Y;}/// <summary>/// 当たり判定/// </summary>/// <param name="p1">パドル左端座標</param>/// <param name="p2">パドル右端座標</param>/// <param name="center">ボール中心</param>/// <param name="radius">ボール半径</param>/// <returns></returns>boolLineVsCircle(Vectorp1,Vectorp2,Vectorcenter,floatradius){VectorlineDir=(p2-p1);//パドルのベクトル(パドルの長さ)Vectorn=newVector(lineDir.Y,-lineDir.X);//パドルの法線n.Normalize();Vectordir1=center-p1;Vectordir2=center-p2;doubledist=Math.Abs(DotProduct(dir1,n));doublea1=DotProduct(dir1,lineDir);doublea2=DotProduct(dir2,lineDir);return(a1*a2<0&&dist<radius)?true:false;}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;}//画面再描画Invalidate();}privatevoidDraw(objectsender,PaintEventArgse)//Draw意味:描画する{SolidBrushpinkBrush=newSolidBrush(Color.HotPink);//SolidBrush(ブラシ)は.Netのクラスで図形を塗り潰すSolidBrushgrayBrush=newSolidBrush(Color.DimGray);//左上からの位置を設定floatpx=(float)this.ballPos.X-ballRadius;//マイナス半径とすることで円の中心になるfloatpy=(float)this.ballPos.Y-ballRadius;//e.描画.円(色, 横, 縦, 物質幅, 物質高さ)e.Graphics.FillEllipse(pinkBrush,px,py,this.ballRadius*2,this.ballRadius*2);//e.描画.長方形(色, 長方形)e.Graphics.FillRectangle(grayBrush,paddlePos);}privatevoidKeyPressed(objectsender,KeyPressEventArgse)//押下毎{if(e.KeyChar=='a')//A押下時{this.paddlePos.X-=20;}elseif(e.KeyChar=='s')//S押下時{this.paddlePos.X+=20;}}}}
複雑なのはベクトル計算ロジックだけ。。
「///」:スラッシュ3つ並んでいる箇所はドキュメントコメントといい、パラメーターやメソッドの説明を記述している。
修正箇所
パドルが画面の外にも移動できるのが気になったので
画面から消えないようにする。
privatevoidKeyPressed(objectsender,KeyPressEventArgse)//押下毎{if(e.KeyChar=='a'&&paddlePos.Left>0)//A押下時{this.paddlePos.X-=20;}elseif(e.KeyChar=='s'&&paddlePos.Right<this.Width)//S押下時{this.paddlePos.X+=20;}}
ブロック作成
参考その3
ブロックを作成後、リスト化してボールが当たれば消えるようにする。
当たり判定は参照その2を流用するので難しいことは特に記述しない。
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Forms;namespaceBreakout{publicpartialclassForm1:Form{VectorballPos;//位置(Vector:2D空間における変位を表す)VectorballSpeed;intballRadius;//半径RectanglepaddlePos;//パドル位置(Rectangle:四角形を作成)List<Rectangle>blockPos;//ブロックの位置(リスト化)publicForm1(){InitializeComponent();//設定したハンドラ等の初期設定this.ballPos=newVector(200,200);this.ballSpeed=newVector(-4,-8);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));}}Timertimer=newTimer();timer.Interval=33;timer.Tick+=newEventHandler(Update);//timer.Trik:Timer有効時に呼ばれるtimer.Start();}/// <summary>/// 内積計算/// </summary>/// <param name="a"></param>/// <param name="b"></param>/// <returns></returns>doubleDotProduct(Vectora,Vectorb){returna.X*b.X+a.Y*b.Y;}/// <summary>/// 当たり判定/// </summary>/// <param name="p1">パドル左端座標</param>/// <param name="p2">パドル右端座標</param>/// <param name="center">ボール中心</param>/// <param name="radius">ボール半径</param>/// <returns></returns>boolLineVsCircle(Vectorp1,Vectorp2,Vectorcenter,floatradius){VectorlineDir=(p2-p1);//パドルのベクトル(パドルの長さ)Vectorn=newVector(lineDir.Y,-lineDir.X);//パドルの法線n.Normalize();Vectordir1=center-p1;Vectordir2=center-p2;doubledist=Math.Abs(DotProduct(dir1,n));doublea1=DotProduct(dir1,lineDir);doublea2=DotProduct(dir2,lineDir);Console.WriteLine(dist);return(a1*a2<0&&dist<radius)?true:false;}intBlockVsCircle(Rectangleblock,Vectorball){if(LineVsCircle(newVector(block.Left,block.Top),newVector(block.Right,block.Top),ball,ballRadius))return1;if(LineVsCircle(newVector(block.Left,block.Bottom),newVector(block.Right,block.Bottom),ball,ballRadius))return2;if(LineVsCircle(newVector(block.Right,block.Top),newVector(block.Right,block.Bottom),ball,ballRadius))return3;if(LineVsCircle(newVector(block.Left,block.Top),newVector(block.Left,block.Bottom),ball,ballRadius))return4;return-1;}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]);}elseif(collision==3||collision==4){ballSpeed.X*=-1;this.blockPos.Remove(blockPos[i]);}}//画面再描画Invalidate();}privatevoidDraw(objectsender,PaintEventArgse)//Draw意味:描画する{SolidBrushpinkBrush=newSolidBrush(Color.HotPink);//SolidBrush(ブラシ)は.Netのクラスで図形を塗り潰すSolidBrushgrayBrush=newSolidBrush(Color.DimGray);SolidBrushblueBrush=newSolidBrush(Color.LightBlue);//左上からの位置を設定floatpx=(float)this.ballPos.X-ballRadius;//マイナス半径とすることで円の中心になるfloatpy=(float)this.ballPos.Y-ballRadius;//e.描画.円(色, 横, 縦, 物質幅, 物質高さ)e.Graphics.FillEllipse(pinkBrush,px,py,this.ballRadius*2,this.ballRadius*2);//e.描画.長方形(色, 長方形)e.Graphics.FillRectangle(grayBrush,paddlePos);//ブロック描画for(inti=0;i<this.blockPos.Count;i++){e.Graphics.FillRectangle(blueBrush,blockPos[i]);}}privatevoidKeyPressed(objectsender,KeyPressEventArgse)//押下毎{if(e.KeyChar=='a'&&paddlePos.Left>0)//A押下時{this.paddlePos.X-=20;}elseif(e.KeyChar=='s'&&paddlePos.Right<this.Width)//S押下時{this.paddlePos.X+=20;}}}}
↓BlockVsCircleのfor文で回しているブロックの生成順
縦(Y軸)に全て並べてから右(X軸)に移る
以下のように横長になってしまった場合、Form1.csのデザインで簡単にフォームのサイズを変更できる。
また、横長にブロックを並べたいときはForm1コンストラクタの「this.Height」を「this.Width」に変更すればよい。
感想
冒頭に1時間あれば終わるって書いてたのに全く足りんかった・・まだまだだな。
Unityでゲーム作ったほうが色々できそうだし楽しそう!