飛び交うレーザー(後編)

こんにちは、きうちです。

今回は前回の「飛び交うレーザー(前編)」の続きをお送りします。

★ ★ ★

前回の
飛び交うレーザー(前編)
では、いろいろな色の線(レーザー)が、画面端で反射しながら画面内を飛び交う、というものを作りました。

今回はこれを、スクリーンセーバーっぽくしてみたいと思います。具体的には、

  • マウスポインタを非表示にする
  • タスクバーボタンを非表示にする
  • フルスクリーンにする
  • 他のアプリよりも前面に表示する
  • ボタン押下またはマウス移動で終了

ということをします。

まず、コンストラクタにこんな風に5行ほど処理を追加します。

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public Form1()
        {
            InitializeComponent();

            var list = new List();
            for (int i = 0; i < COLORS.Length; i++)
            {
                Laser l = MakeLaser(COLORS[i]);
                list.Add(l);
            }
            ll = list.ToArray();

            Cursor.Hide(); // マウスポインタを非表示にする
            this.ShowInTaskbar = false; // タスクバーボタンを非表示にする
            this.FormBorderStyle = FormBorderStyle.None; // フォームをタイトルバーなし、フチなしにする
            this.WindowState = FormWindowState.Maximized; // フォームを最大化する
            this.TopMost = true; // 他のアプリよりも前面に表示する

            timer1.Start(); // タイマーをスタートさせる
        }

実行してみます。

画面全体に表示されるようになりました!

ただ、×ボタンがないのでマウスクリックで終わらせることができません。
終了させるときはALT+F4を押してください。
もしくはALT+TABでVisualStudioに切り替えてから停止ボタンをクリックしてください。

★ ★ ★

続いて、「ボタン押下またはマウス移動で終了」の部分。
フォームにKeyPressイベントとMouseMoveイベントの処理を入れます。

        /// <summary>
        /// キー押下時処理
        /// </summary>
        /// <param name="sender">イベント発生オブジェクト</param>
        /// <param name="e">イベント引数</param>
        private void Form1_KeyPress(object sender, KeyPressEventArgs e)
        {
            this.Dispose();
        }

        /// <summary>マウスポイント移動カウント</summary>
        private int MouseMoveCount = 0;

        /// <summary>現在のマウスポインタの位置</summary>
        private Point currentMouseP = Point.Empty;

        /// <summary>
        /// マウスポインタ移動イベント
        /// </summary>
        /// <param name="sender">イベント発生オブジェクト</param>
        /// <param name="e">イベント引数</param>
        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            // マウスポインタの位置を取得する
            int x = System.Windows.Forms.Cursor.Position.X;
            int y = System.Windows.Forms.Cursor.Position.Y;

            // マウスポインタが移動していたら処理
            if (currentMouseP.X != x || currentMouseP.Y != y)
            {
                MouseMoveCount++;
                currentMouseP = new Point(x, y);
                if (MouseMoveCount == 2) // 2回動いたら終了
                {
                    this.Dispose();
                }
            }
        }

実行すると、キーボード押下またはマウス移動で終了します。

マウスポインタが「2回動いたら終了」としていますが、これはやってみたらどうも起動した瞬間マウスを動かしていなくてもMouseMoveイベントが発生してしまっているようですぐ終了してしまったので入れてみました。

★ ★ ★

スクリーンセーバーといえば、設定画面でいろいろ設定をいじれることが多いですよね。

この「反射レーザー」にも設定を持たせてみましょうか。

設定できるのは、

  • レーザーの数
  • レーザーの長さ

あたりにしておきましょうか。

次のような画面をプロジェクトに追加します。

入力欄はNumericUpDownにします。名前、既定値、最小値、最大値は以下の表のようにします。

説明 名前 既定値 最小値 最大値
レーザーの数 numCount 7 1 100
レーザーの長さ numLength 7 2 16

OKボタン押下で、設定が保存されるようにします。
キャンセルボタンはただ単に画面を閉じるようにします。
起動時に設定ファイルが存在する場合はそれを読み込んで入力欄に反映させます。

    public partial class FormSettings : Form
    {
        public FormSettings()
        {
            InitializeComponent();

            int count = (int)numCount.Value, length = (int)numLength.Value;
            SettingsManager.Load(ref count, ref length);
            numCount.Value = count;
            numLength.Value = length;
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            SettingsManager.Save(numCount.Value, numLength.Value);
            Dispose();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            Dispose();
        }
    }


設定処理は別途、SettingsManager.cs というファイルで定義します。

    internal class SettingsManager
    {
        internal static void Save(decimal count, decimal length)
        {
            string? dirPath = Directory.GetParent(Application.ExecutablePath)?.FullName;
            if (dirPath != null)
            {
                string filepath = Path.Combine(dirPath, "ReflectionLaser.txt");
                File.WriteAllText(filepath, $"{count},{length}");
            }
        }

        internal static void Load(ref int count, ref int length)
        {
            try
            {
                string? dirPath = Directory.GetParent(Application.ExecutablePath)?.FullName;
                if (dirPath != null)
                {
                    string filepath = Path.Combine(dirPath, "ReflectionLaser.txt");
                    if (File.Exists(filepath))
                    {
                        string text = File.ReadAllText(filepath);
                        string[] values = text.Split(',');
                        if (values.Length == 2)
                        {
                            count = int.Parse(values[0]);
                            length = int.Parse(values[1]);
                        }
                    }
                }
            }
            catch { }
        }
    }


本体の方では、必要なパラメータをフィールドで定義しておいて、コンストラクタで設定の読み込みを行い、読み込んだ値をパラメータに反映させます。

    public partial class Form1 : Form
    {
        /// <summary>レーザーの色</summary>
        private static Pen[] COLORS = { Pens.Blue, Pens.Red, Pens.Magenta, Pens.Green, Pens.Cyan, Pens.Yellow, Pens.White };

        /// <summary>レーザーの本数</summary>
        private int count = COLORS.Length;

        /// <summary>1本のレーザーを構成する要素数</summary>
        private int length = 16;

        /// <summary>レーザーの1要素の長さ(X方向とY方向のそれぞれの幅)</summary>
        private int xyLength = 16;

        /// <summary>レーザーの移動速度</summary>
        private int velocity = 16;

        /// <summary>レーザーのデータ</summary>
        private Laser[] lasers;

        /// <summary>乱数生成オブジェクト</summary>
        private Random random = new Random();

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public Form1()
        {
            InitializeComponent();
            SettingsManager.Load(ref count, ref length);

            var list = new List<Laser>();
            for (int i = 0; i < count; i++)
            {
                Laser l = MakeLaser(COLORS[i % COLORS.Length], length, xyLength);
                list.Add(l);
            }
            lasers = list.ToArray();

            Cursor.Hide(); // マウスポインタを非表示にする
            this.ShowInTaskbar = false; // タスクバーボタンを非表示にする
            this.FormBorderStyle = FormBorderStyle.None; // フォームをタイトルバーなし、フチなしにする
            this.WindowState = FormWindowState.Maximized; // フォームを最大化する
            this.TopMost = true; // 他のアプリよりも前面に表示する

            timer1.Start(); // タイマーをスタートさせる
        }

        /// <summary>
        /// レーザーのデータを作る
        /// </summary>
        /// <param name="pen">レーザーの色</param>
        /// <returns>レーザーのデータ</returns>
        private Laser MakeLaser(Pen pen, int length, int xyLength)
        {
            var x = random.Next(this.ClientSize.Width / 2, this.ClientSize.Width);
            var y = random.Next(this.ClientSize.Height / 2, this.ClientSize.Height);

            var pp = new List<Point>();
            var vv = new List<Point>();
            for (int i = 0; i < length; i++)
            {
                pp.Add(new Point(x, y));
                x -= xyLength;
                y -= xyLength;
                vv.Add(new Point(velocity, velocity));
            }

            return new Laser(pp.ToArray(), vv.ToArray(), pen);
        }

前回は、レーザーのデータを ll (小文字のL2つ)というフィールド名で保持していましたが、さすがにわかりづらいので(なんでこんな命名をしてしまったのか)、lasersとしました。
あしからず、読み替えのほどを…m(_ _)m

さて、あとは設定画面を起動前に表示するようにしてみます。

Program.cs をこんな風にします。

    internal static class Program
    {
        [STAThread]
        static void Main()
        {
            ApplicationConfiguration.Initialize();
            Application.Run(new FormSettings());
            Application.Run(new Form1());
        }
    }

起動すると、まずは設定画面が出ます。

レーザーの数を100として、OKボタンをクリックしてみます。

レーザーの数、増えましたね!

★ ★ ★

最後に、このプログラムを"がち"スクリーンセーバー、つまりWindowsにインストールして動かせるやつにしようと思ったんですけど、さすがに説明を要する追加がたくさん必要になるので、ここに載せるのはやめることにしました。のちのちソースを公開しますので、そのときはまたこのブログでお知らせさせていただこうと思います。よかったら試してみてください^^

それでは!

追伸
このブログで書いていた内容をソースコードにしたものは以下で公開しております。よかったら。

こちら。