【C#】パックマン風スネークゲームを作ろう! メモ帳でプログラミング

C#ライン

パックスネークゲーム

今回の記事もメモ帳を使ったC#プログラミングをご紹介します。作成するゲームはパックマンとスネークゲームを合わせた「PACSNAKE」というオリジナルのゲームです。作成するうえで用意する画像は何もありません。コードでパックマンやエサやスコア表示などを描画します。コード全文を掲載しますのでぜひ昔懐かしのレトロ風ゲームで遊んでみてください!

■遊び方

プログラムを実行するとスタート画面が表示されます。

ゲームがスターとすると中央にパックマンが止まったまま表示されます。
キー操作は「↑」「→」「↓」「←」でパックマンを移動させます。
なにかキーをクリックするとパックマンがその方向に進みゲームが開始されます。
大きいエサを食べるとパックマンが後ろに増えていきます。
小さいエサを全部食べるとラウンドクリアです。次のラウンドに進むたびに小さいエサが増えていきます。
パックマンが画面四方の壁に激突するか自分自身にぶつかるとゲームオーバーです。
ゲームオーバすると「REPLAY」ボタンがクリックでるようになりクリックするとゲームが最初から開始されます。

■準備

コンパイラとファイル作成の記事

以前の記事を参考にC# を実行させるためのコンパイラのbatファイルを作成します。次も以前の記事を参考にプログラムを実行するファイルを作成します。ファイル名は「PacSnake.cs」にしましょう。この記事の最後に全コードを掲載していますのでメモ帳に全てコピペし保存します。
そして下記コマンドを実行します。

>cs pacsnake.cs

コンパイルが成功し「PacSnake.exe」の実行ファイルが作成されます。

「PacSnake.exe」をクリックするとゲームが開始されます。

■全コード

下記がプログラムの全コードです。コードの詳細説明はしませんがコメントアウトで簡単に処理の説明を記述してあります。

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Collections.Generic;

class PacSnake : Form
{
	static readonly int SIZE   = 24;
	static readonly int WIDTH  = 26;
	static readonly int HEIGHT = 26;
	static readonly int SCORE  = SIZE * 7;
	static readonly int TIMER  = 200;
  	static readonly int MINIFOOD = 30;

	int mapX  = WIDTH  / 2;
	int mapY  = HEIGHT / 2;
	int mapDX = 0;
	int mapDY = 0;
	int mapFoodX;
	int mapFoodY;
	int pacAngle  = 210;
	int pacMouth  = 300;
	int getFoods = 0;
	int restFoods = MINIFOOD;
	int round = 1;
	Random mapRnd = new Random();
	bool gameFlag = true;

	List pacBody = new List();
	// 小さいエサを格納するリスト
	List miniFoods = new List(); 
	//クラスのフィールドとして宣言(ここで定義する)
	Button replayBtn = new Button(); 

	public PacSnake()
	{
        //フォームサイズ
    	Size = new Size(SIZE * WIDTH + SCORE , SIZE * HEIGHT + SIZE * 2);
        //フォームのタイトル表示
        this.Text = "PACSNAKE";
        //画面のちらつき防止
    	DoubleBuffered = true;
		StartPosition = FormStartPosition.Manual;
		// フォームの左上の位置に設定
    	Location = new Point(0, 0); 
		// サイズ変更を禁止する
		FormBorderStyle = FormBorderStyle.FixedSingle; 

    	//大エサを追加
    	addBigFood();

    	// 小エサを追加
    	for (int i = 0; i < MINIFOOD; i++)
    	{
      		addMiniFoods();
    	}

        pacBody.Add(new Point(mapX, mapY));
        Timer timer = new Timer();
        timer.Interval = TIMER;
        timer.Tick += Timer_Tick;

        timer.Start();
		 
		// リプレイボタンの配置
    	replayBtn.Text = "REPLAY";
    	replayBtn.Size = new Size(SIZE * 4, SIZE * 2);
    	replayBtn.Location = new Point(SIZE * WIDTH + SIZE, SIZE * 20); 
        //リプレイボタンをクリックしてreplayBtn_Clickメソッド呼出し
    	replayBtn.Click += replayBtn_Click; 
		// ボタンをフォームに追加
    	Controls.Add(replayBtn); 
		// ボタンを無効にする。有効だとキー入力できない
		replayBtn.Enabled = false;
    }

    void addBigFood()
    {
        mapFoodX = mapRnd.Next(WIDTH - 2) ;
        mapFoodY = mapRnd.Next(HEIGHT - 2);
    }

    void addMiniFoods()
    {
        int x = mapRnd.Next(WIDTH);
        int y = mapRnd.Next(HEIGHT);

        // エサの位置が他のエサやパックマンの位置と被らないようにする
        while (pacBody.Contains(new Point(x, y)) || 
              (x == mapFoodX && y == mapFoodY))
        {
            x = mapRnd.Next(WIDTH);
            y = mapRnd.Next(HEIGHT);
        }
        //小エサをマップに配置
        miniFoods.Add(new Point(x , y ));
    }

    //ゲームのメインループ
    void gameLoop()
    {
        mapX += mapDX;
        mapY += mapDY;

        // 大エサを食べたか判定
        if (mapX == mapFoodX && mapY == mapFoodY)
        {
            addBigFood();
            pacBody.Insert(0, new Point(mapX, mapY));
        }
        else
        {
            pacBody.RemoveAt(pacBody.Count - 1);
            pacBody.Insert(0, new Point(mapX, mapY));
        }

        // 小エサを食べたかチェック
        foreach (Point miniFood in miniFoods)
        {
            if (mapX == miniFood.X && mapY == miniFood.Y)
            {
                miniFoods.Remove(miniFood);
                getFoods++;
                restFoods--;

				//小エサを全部たべたらラウンドクリア
            	if (restFoods == 0)
            	{
              		roundClear();
			  		return; // ループから抜ける
            	}
                break;
            }
        }

        // 壁との衝突チェック
        if (mapX < 0 || mapX >= WIDTH || 
            mapY < 0 || mapY >= HEIGHT)
        {
            gameFlag = false;
        }

        // 自分自身との衝突チェック
        for (int i = 1; i < pacBody.Count; i++)
        {
            if (mapX == pacBody[i].X && mapY == pacBody[i].Y)
            {
                gameFlag = false;
                break;
            }
        }
        Invalidate();
    }

    void roundClear()
    {
        round++;
		// 小エサのリストをクリア
		miniFoods.Clear(); 
    	// 小エサを追加(ラウンド数かけるMINIFOOD)
        int createFoods = MINIFOOD * round;
    	for (int i = 0; i < createFoods; i++)
    	{
      		addMiniFoods();
    	}

        //小エサの数を初期化
        restFoods = createFoods;
        //再描画
		Invalidate(); 
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        g.Clear(Color.Black);
        SolidBrush brush = new SolidBrush(Color.Yellow);

        //画面外周の枠の描画
        g.FillRectangle(Brushes.Cyan, 0, 0, 
            SIZE * WIDTH , SIZE / 6 );  //上面
        g.FillRectangle(Brushes.Cyan, 0, 0, 
            SIZE / 6, SIZE * HEIGHT ); //左面
        g.FillRectangle(Brushes.Cyan, 0, SIZE * HEIGHT, 
            SIZE * WIDTH, SIZE / 6);  //下面
        g.FillRectangle(Brushes.Cyan, SIZE * WIDTH , 0, 
            SIZE / 6, SIZE * HEIGHT ); //右面

        //パックマン描画
        g.FillPie(brush, mapX * SIZE , mapY * SIZE , 
            SIZE, SIZE, pacAngle, 300); 
    //大エサ描画
        g.FillEllipse(Brushes.Yellow, mapFoodX * SIZE, mapFoodY * SIZE, 
            SIZE - SIZE / 3, SIZE - SIZE / 3);

        // 小エサ描画
        foreach (var miniFood in miniFoods)
        {
            g.FillEllipse(Brushes.White, miniFood.X * SIZE + SIZE / 4, 
                miniFood.Y * SIZE + SIZE / 4, SIZE / 4, SIZE / 4);
        }

        //パックマンの体を描画
        foreach (var segment in pacBody)
        {
            //パックマンの向き 右=30, 下=120, 左=210, 上=300                
            g.FillPie(brush, segment.X * SIZE, 
                segment.Y * SIZE, SIZE, SIZE, pacAngle, pacMouth); 
        }

        //スコアパネル部表示
        Font fnt1 = new Font("メイリオ", 12);
        Font fnt2 = new Font("メイリオ", 10);
        g.DrawString("ROUND      " + round, fnt1, brush, 
            SIZE * WIDTH + WIDTH, SIZE * 1);
        g.DrawString("PACBODY   " + pacBody.Count, fnt1, brush, 
            SIZE * WIDTH + WIDTH, SIZE * 3);
        g.DrawString("GETFOOD   " + getFoods, fnt1, brush, 
            SIZE * WIDTH + WIDTH, SIZE * 4);
        g.DrawString("RESTFOOD " + restFoods, fnt1, brush, 
            SIZE * WIDTH + WIDTH, SIZE * 6);
        g.DrawString("©2024-sumiox.com", fnt2, brush, 
            SIZE * WIDTH + WIDTH/2, SIZE * WIDTH - WIDTH / 2);
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        base.OnKeyDown(e);

        if (e.KeyCode == Keys.Left && mapDX == 0) 
            { mapDX = -1; mapDY = 0; 
              pacAngle = 210; pacMouth = 300;}
        else if (e.KeyCode == Keys.Right && mapDX == 0) 
            { mapDX = 1; mapDY = 0; 
              pacAngle = 30; pacMouth = 300;}
        else if (e.KeyCode == Keys.Up && mapDY == 0) 
            { mapDX = 0; mapDY = -1; 
              pacAngle = 300; pacMouth = 300;}
        else if (e.KeyCode == Keys.Down && mapDY == 0) 
            { mapDX = 0; mapDY = 1; 
              pacAngle = 120; pacMouth = 300;}
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        if (gameFlag)
        {
            gameLoop();
        }
        else
        {
            // ゲームオーバー時にボタンを有効にする
            replayBtn.Enabled = true; 
            //ゲームオーバの表示
            gameMsg("GAME OVER");
            ((Timer)sender).Stop(); 
        }
    }

    public void gameMsg(string msg)
    {   
        using (Graphics g = this.CreateGraphics())
        {
            SolidBrush brush = new SolidBrush(Color.Red);
            Font f = new Font("メイリオ", 34, FontStyle.Bold);
            g.DrawString(msg, f, brush, 
                WIDTH * SIZE / 4 , HEIGHT * SIZE / 3);
        }
    }

    private void replayBtn_Click(object sender, EventArgs e)
    {
        // ゲームの状態をリセット
        mapX = WIDTH / 2;
        mapY = HEIGHT / 2;
        mapDX = 0;
        mapDY = 0;
        pacAngle = 210;
        pacMouth = 300;
        getFoods = 0;
	    restFoods = MINIFOOD;
        round = 1;
        gameFlag = true;

        // 小エサのリストをクリア
        miniFoods.Clear();

        // 小エサを追加
        for (int i = 0; i < MINIFOOD; i++)
        {
            addMiniFoods();
        }

        // パックボディのリストをリセット
        pacBody.Clear();
        pacBody.Add(new Point(mapX, mapY));

        // 再びリプレイボタンを無効にする
        replayBtn.Enabled = false;



        // ゲームループを再開
        Timer timer = new Timer();
        timer.Interval = TIMER;
        timer.Tick += Timer_Tick;
        timer.Start();



        // 再描画
        Invalidate();
    }	

    static void Main()
    {
        Application.Run(new PacSnake());
    }
}

■まとめ

いかがだったでしょうか。このようにメモ帳プログラミングで昔懐かしのレトロゲームを作成することができます。コード量はちょっと多めですがゲームの作り方がちょっとでも伝わればと思います。この機会にゲームプログラミングをはじめてみましょう!

コメント