C#でゲームをつくろう!
ということでゲームをつくってみることにします。検索してみるとインベーダーゲームの作成方法を解説しているサイトはたくさんありますが、ギャラクシアンをつくっているページはそんなにないので、これをつくることにします。完成品はこんな感じになります。
VIDEO
どうやって作るか?
どうやって作るか? ピクチャーボックスに敵キャラや自機を描画してこれを移動させるという極めて安易な方法でやってみることにします。
敵キャラや自機、弾丸はピクチャーボックスを使う
タイマーをつかってこれらを移動させる
あたり判定をして得点加算やミス時の処理をおこなう
デザイナで以下のようなものをつくります。
敵は複数なのでイメージをコピーしてつくります。
自機を移動させる
操作方法は←→キーとスペースキー、Sキーをつかいます。矢印キーで左右の操作、スペースキーで弾丸を発射します。ゲームスタートは「S」キーを使います。これはformのKeyDownイベントを使えば簡単にできそうです。
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
public Form1 ( )
{
InitializeComponent ( ) ;
KeyDown += Form1_KeyDown ;
}
private void Form1_KeyDown ( object sender , KeyEventArgs e )
{
if ( e . KeyCode == Keys . Left )
{
MoveLeft ( ) ;
}
else if ( e . KeyCode == Keys . Right )
{
MoveRight ( ) ;
}
else if ( e . KeyCode == Keys . Space )
{
}
else if ( e . KeyCode == Keys . S )
{
}
}
void MoveLeft ( )
{
Point pt = pictureBoxSpaceship . Location ;
pt . X -= 10 ;
pictureBoxSpaceship . Location = pt ;
}
void MoveRight ( )
{
Point pt = pictureBoxSpaceship . Location ;
pt . X += 10 ;
pictureBoxSpaceship . Location = pt ;
}
一応、これでも動くのですが、これだとキーを押し続けたときに最初に少しだけ動き、そのあとしばらくして連続で動くという変な動き方になります。
改善策として「左のキーが押されている」「右のキーが押されている」「どちらも押されていない」という状態を保存するフィールド変数を用意します。タイマーをつかって現在どの状態なのか調べて動かすようにします。
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
public enum Direct
{
None = 0 ,
Left = 1 ,
Right = 2 ,
}
Direct direct = Direct . None ;
public Form1 ( )
{
InitializeComponent ( ) ;
KeyDown += Form1_KeyDown ;
KeyUp += Form1_KeyUp ;
timer1 . Tick += Timer1_Tick ;
timer1 . Interval = 50 ;
}
private void Form1_KeyDown ( object sender , KeyEventArgs e )
{
if ( e . KeyCode == Keys . Left )
{
direct = Direct . Left ;
}
else if ( e . KeyCode == Keys . Right )
{
direct = Direct . Right ;
}
else if ( e . KeyCode == Keys . Space )
{
Bulletlaunch ( ) ; // あとで考える
}
else if ( e . KeyCode == Keys . S )
{
GameStart ( ) ;
}
}
private void Form1_KeyUp ( object sender , KeyEventArgs e )
{
if ( e . KeyCode == Keys . Left )
{
direct = Direct . None ;
}
else if ( e . KeyCode == Keys . Right )
{
direct = Direct . None ;
}
}
void GameStart ( )
{
timer1 . Start ( ) ;
}
ゲームがスタートするときにタイマーをStartさせます。Tickイベントが起きたら
private void Timer1_Tick ( object sender , EventArgs e )
{
SpaceshipMove ( ) ;
}
void SpaceshipMove ( )
{
if ( direct == Direct . Left )
{
MoveLeft ( ) ;
}
else if ( direct == Direct . Right )
{
MoveRight ( ) ;
}
}
これでうまく自機が動くようになりました。
自機から弾丸を発射させる
スペースキーを押すと自機から弾丸が発射されるようにします。発射された弾丸は上昇していきます。
まず自機から弾丸を発射させるメソッドを完成させます。
void Bulletlaunch ( )
{
// さてどうしたものか?
}
弾丸は自機の上部中心から発射されます。そこで上部中心の座標をもとめます。
Point pt = pictureBoxSpaceship . Location ;
int width = pictureBoxSpaceship . Size . Width ;
int centerX = pt . X + width / 2 ;
centerXが弾丸の中心になるようにする必要があります。弾丸のサイズを決めてしまいましょう。
int BULLET_WIDTH = 2 ;
int BULLET_HEIGHT = 10 ; // フィールド変数
void Bulletlaunch ( )
{
Point pt = pictureBoxSpaceship . Location ;
int width = pictureBoxSpaceship . Size . Width ;
int centerX = pt . X + width / 2 ;
//centerX- BULLET_WIDTH/2
Point point = new Point ( centerX - BULLET_WIDTH / 2 , pt . Y ) ;
PictureBox bullet = new PictureBox ( ) ;
bullet . Location = point ;
bullet . Size = new Size ( BULLET_WIDTH , BULLET_HEIGHT ) ;
bullet . BackColor = Color . White ;
bullet . Parent = panel1 ;
}
これで弾丸は自機の向こう側に表示されます(自機を移動させてみるとわかる)。これでは発射されたというよりパネルのうえに置かれただけなので、上昇させる方法を考えます。
弾丸を上昇させる
弾丸は単発ではなく複数発射されます。そこでリストに格納してまとめて処理してしまいましょう。
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
List < PictureBox > _bullets = new List < PictureBox > ( ) ;
List < PictureBox > Bullets
{
get {
_bullets = _bullets . Where ( x => ! x . IsDisposed ) . ToList ( ) ;
return _bullets ;
}
}
void Bulletlaunch ( )
{
Point pt = pictureBoxSpaceship . Location ;
int width = pictureBoxSpaceship . Size . Width ;
int centerX = pt . X + width / 2 ;
Point point = new Point ( centerX - BULLET_WIDTH / 2 , pt . Y ) ;
PictureBox bullet = new PictureBox ( ) ;
bullet . Location = point ;
bullet . Size = new Size ( BULLET_WIDTH , BULLET_HEIGHT ) ;
bullet . BackColor = Color . White ;
bullet . Parent = panel1 ;
Bullets . Add ( bullet ) ; // これを追加した。
}
void BulletsMove ( )
{
foreach ( var bullet in Bullets )
{
Point pt = bullet . Location ;
pt . Y -= 10 ;
bullet . Location = pt ;
if ( bullet . Location . Y < -BULLET_HEIGHT )
bullet . Dispose ( ) ;
}
}
画面上まで上昇した弾丸は必要ないのでDisposeしてしまいます。またプロパティの処理でDisposeされた弾丸はリストのなかから排除しています。
最後にTimer1_TickにBulletsMoveを追加します。
private void Timer1_Tick ( object sender , EventArgs e )
{
SpaceshipMove ( ) ;
BulletsMove ( ) ;
}
これで自機から発射された弾丸は上昇できるようになりました。めでたしめでたし。続きは次回。
鳩でも分かるC#管理人からのお願い
できる仕事であれば請け負います。鳩でもわかるC#管理人はクラウドワークスに在宅ワーカーとして登録しています。お仕事の依頼もお待ちしております。
⇒ 仕事を依頼する
コメントについて
コメントで英語などの外国語でコメントをされる方がいますが、管理人は日本語以外はわからないので基本的に内容が理解できず、承認することもありません。それからへんな薬を売っているサイトやリンク先のサイトが存在しないというスパムコメントも多々あります。
Some people make comments in foreign languages such as English, but since the manager does not understand anything other than Japanese, he basically cannot understand the content and does not approve it. Please use Japanese when making comments.
そんななか日本語のコメントもいただけるようになりました。「○○という変数はどこで宣言されているのか?」「××というメソッドはどこにあるのか」「例外が発生する」「いっそのことソース丸ごとくれ」という質問ですが、管理人としては嬉しく思います。「自分が書いた記事は読まれているんだな」と。疑問点には可能な限り答えます。記事に問題があれば修正いたします。
そのうえでお願いがあります。「匿名」という味も素っ気もない名前ではなく、捨てハンでいいのでなにかハンドルネームをつくってほしいと思います。
管理人のモチベーションアップのために
よろしければご支援お願いします。
⇒ 管理人の物乞いリスト