ゲーム実装で身に付くプログラミング 第12回

Spriteクラスで色ブロックを作る

Spriteクラスで色ブロックを作る

赤緑青の色ブロックは3つともSpriteクラスで設計図を作ります。これまでの連載と違い、珍しくSpriteクラス内で画像を読み込みます。本来ならこの方が機能分けがはっきりして良いと思います。

・サンプルコード「Sprite.cs」ファイル

using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using System;

namespace colorWinUI
{
    public class Sprite
    {
        public int x, y;
        readonly int vx, vy;
        public int color;
        readonly Random rand = new();

        public Sprite(Canvas canvas, int c)
        {
            x = rand.Next(0, 1700);
            y = rand.Next(0, 800);
            vx = rand.Next(-4, 4);
            vy = rand.Next(-4, 4);
            color = c;
            var image = new Image
            {
                Source = new BitmapImage(new Uri($"ms-appx:///Assets/{c}.png")),
                Width = 100,
                Height = 100,
                Stretch = Microsoft.UI.Xaml.Media.Stretch.Uniform,
            };
            canvas.Children.Add(image);
            Update(canvas,canvas.Children.Count-1);
        }

        public int Update(Canvas canvas,int i)
        {
            return 0;
        }
    }
}

【サンプルコードの解説】
色ブロックの(x,y)座標、1コマの移動量(vx,vy)、ブロックの色「color」、ランダムはnew()だけでシンプルにコンストラクタを呼び出せます。
「BitmapImage」クラスでAssetsフォルダにある0~2.pngの赤緑青ブロック画像を読み込み、「Image」に設定します。
キャンバス(ここではxamlファイルのImageCanvas)の子にimageを追加します。

・サンプルコード「MainWindow.xaml.cs」ファイル

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using Windows.Media.Core;
using Windows.Media.Playback;

namespace colorWinUI
{
    public sealed partial class MainWindow : Window
    {
        DispatcherTimer timer = new();
        List<Sprite> sprites = new();
        int stage = 2;
        readonly List<MediaPlayer> sounds = new();

        public MainWindow()
        {
            InitializeComponent();
            var appWindow = this.AppWindow;
            appWindow.Resize(new Windows.Graphics.SizeInt32(1800, 900));
            // 10FPSごとに呼び出すタイマー
            timer.Interval = TimeSpan.FromMilliseconds(100);
            timer.Tick += Timer_Tick;
            string[] path = {
                "GameStart.ogg",
                "GameOver.ogg",
                "StageClear.ogg",
                "Mix.ogg",
            };
            for (int i = 0; i < path.Length; i++) {
                sounds.Add(new MediaPlayer());
                sounds[i].Source = MediaSource.CreateFromUri(new Uri($"ms-appx:///Assets/{path[i]}"));
            }
        }

        private void TitleImage_PointerPressed(object sender, PointerRoutedEventArgs e)
        {
            TitleGrid.Visibility = Visibility.Collapsed;
            Init();
            timer.Start();
            sounds[0].Play();
        }

        void Init()
        {
            ImageCanvas.Children.Clear();
            sprites.Clear();
            for (int i = 0; i < 3 * stage; i++)
            {
                sprites.Add(new Sprite(ImageCanvas, i % 3));
                int count = ImageCanvas.Children.Count - 1;
                ImageCanvas.Children[count].PointerPressed += Child_PointerPressed;
            }
        }

        void Child_PointerPressed(object sender, PointerRoutedEventArgs e)
        {
        }

        void Timer_Tick(object? sender, object e)
        {
        }
    }
}

【サンプルコードの解説】
「DispatcherTimer」クラスで「timer」プロパティにループ処理するタイマーをセットし、100ミリ秒ごと(10FPS)に「Timer_Tick」メソッドを呼び出します。
「List<Sprite>」はクラスのインスタンスを配列で管理できる「コレクション」で、「sprites」プロパティにインスタンスを代入します。
「stage」プロパティは赤緑青ブロック2個ずつが最初のステージとなる値です。
「List<MediaPlayer>」コレクションのインスタンスをサウンドの「sounds」プロパティに代入し、MediaPlayerでAssetsフォルダからすべてのサウンドを事前に読み込みます。
タイトル画像がマウスで押されたら「TitleImage_PointerPressed」が呼ばれ、ゲームを開始します。
「Init」メソッドでspritesプロパティにSpriteクラスのインスタンスを代入し、タイトル画像を非表示にして"GameStart.ogg"サウンドを再生します。その色ブロックがマウスで押されたらChild_PointerPressedが呼ばれます。

色ブロックを移動する

Spriteコンストラクタで色ブロックの位置と移動量はランダムに決めましたが、まだ反映させていません。ここでは色ブロックを毎フレーム移動して配置に反映します(図2)。

図2:色ブロックを表示

・サンプルコード「Sprite.cs」ファイル

using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using System;

namespace colorWinUI
{
    public class Sprite
    {
(中略)

        public Sprite(Canvas canvas, int c)
        {
(中略)
        }

        public int Update(Canvas canvas,int i)
        {
            var image = (Image)canvas.Children[i];
            x += vx;
            y += vy;
            Canvas.SetLeft(image, x);
            Canvas.SetTop(image, y);
            if (x >= -100 && y >= -100 && x < 1800 && y < 900) return 1;
            return 0;
        }
    }
}

【サンプルコードの解説】
「Update」メソッドでImageCanvasの子要素である色ブロックを移動・配置し、ウィンドウ内にあるかを判定します。

・サンプルコード「MainWindow.xaml.cs」ファイル

(前略)

namespace colorWinUI
{
    public sealed partial class MainWindow : Window
    {
(中略)

        public MainWindow()
        {
(中略)
        }

        private void TitleImage_PointerPressed(object sender, PointerRoutedEventArgs e)
        {
(中略)
        }

        void Init()
        {
(中略)
        }

        void Child_PointerPressed(object sender, PointerRoutedEventArgs e)
        {
        }

        void Timer_Tick(object? sender, object e)
        {
            var flg = 0;
            for (int i = 0; i < sprites.Count; i++)
            {
                flg += sprites[i].Update(ImageCanvas,i);
            }
            // ゲームオーバー判定
            if (flg == 0)
            {
                timer.Stop();
                TitleGrid.Visibility = Visibility.Visible;
                sounds[1].Play();
            }
        }
    }
}

【サンプルコードの解説】
タイマーでTimer_Tickメソッドが1秒間にほぼ10回呼ばれるたびに色ブロックを移動し、ウィンドウ内に色ブロックが1個もなければ(flgが0)、ゲームオーバー処理("GameOver.ogg"サウンドを再生しタイトル画像を表示)を行います。

【コラム】アート

大西拓磨という芸術家が「人と面白くコミュニケーションできないから、面白いものを生み出すしかない。アートは最後の砦」みたいに言っていました。まさに僕もそんな感じです。

人気記事トップ10

人気記事ランキングをもっと見る

企画広告も役立つ情報バッチリ! Sponsored