windowsのスクリーンセーバーを作る

## 令和の時代にスクリーンセーバーの作り方を知る
プライバシーマークやISMS等でお世話になっているスクリーンセーバーですが、実際にはどのような仕組みなで動いているのか知る人は少ないと思います（私もそうでした）

エンジニアなら誰しも一度は作ってみたいと思うスクリーンセーバー。人生でやり残したことのひとつなので作っておきたい。

今回は作成方法をAIに教えてもらいながら作成を進めていく形で作り方を学んでいきます。

:::note info
**その昔**
スクリーンセーバーは、デスクトップアクセサリツール（壁紙、アイコン含む）として販売され、ガチャ10連分相当の価格で取引されていました。

その後、PCを利用する多くの人達は個性むき出しにする事を辞めてしまい（ソーシャルに向かったのかな？）壁紙やスクリーンセーバーもWindows標準のもので事足りている方が多いのではないでしょうか。
:::

### 作りたいもの
windows98の頃にあったスクリーンセーバー『[Starfield Simulation](https://www.screensaversplanet.com/screensavers/starfield-simulation-495/)』にチャレンジします。

### 利用するもの

* VisualStudio 2022
* C#
* .NET8
* ChatGPT4


### 実際に聞いてみる
プロンプトを書いている最中「スクリーンセーバーとはどのような機能を有しているのか」という点について今まで考えた事がなかったので、思いつく限りの事を書いてみた。


``` プロンプト.txt
windows98の頃にあったスクリーンセーバー「Starfield Simulation」を以下の仕様で作成したいです。
スクリーンセーバーに必要な機能を備えたプログラムを書いて下さい。

#ツール・言語
VisualStudio 2022
C#
.NET8

#考慮すべき点
キーボード、マウスの入力からの画面復帰
多重起動を行わない

```

#### ChatGPTさんの回答
> Windows 98の「Starfield Simulation」スクリーンセーバーは、宇宙を飛行しているような視覚効果をシンプルに表現したもので、非常に人気がありました。Visual Studio 2022 と C#、.NET 8 を使用してこれを実装するための基本的なスクリーンセーバーのプログラムを下記に示します。

このスクリーンセーバーの良さを理解してくれているとは思っていなかったので友達になりたいくらい嬉しい。
ここまで理解してくれているのであればきっと素晴らしいスクリーンセーバーを再現してくれるであろう。
そしてかなり雑な質問であるにも関わらず、AIが動きそうなプログラムを出力してくれた。

```出力されたコード.cs
using System;
using System.Drawing;
using System.Windows.Forms;

namespace StarfieldSimulation
{
    public partial class MainForm : Form
    {
        private Timer timer;
        private Random rand = new Random();
        private int screenWidth = Screen.PrimaryScreen.Bounds.Width;
        private int screenHeight = Screen.PrimaryScreen.Bounds.Height;

        public MainForm()
        {
            InitializeComponent();
            this.FormBorderStyle = FormBorderStyle.None;
            this.Bounds = Screen.PrimaryScreen.Bounds;
            this.TopMost = true;

            timer = new Timer();
            timer.Interval = 10;  // 更新頻度（ミリ秒）
            timer.Tick += Timer_Tick;
            timer.Start();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            this.Invalidate();  // 画面を再描画
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            e.Graphics.FillRectangle(Brushes.Black, this.ClientRectangle);

            for (int i = 0; i < 100; i++)  // 星の数
            {
                int x = rand.Next(screenWidth);
                int y = rand.Next(screenHeight);
                e.Graphics.FillRectangle(Brushes.White, x, y, 2, 2);  // 星のサイズ
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            Application.Exit();  // キーボード入力で終了
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            Application.Exit();  // マウス移動で終了
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            Application.Exit();  // マウスクリックで終了
        }

        [STAThread]
        static void Main()
        {
            if (System.Diagnostics.Process.GetProcessesByName(System.IO.Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetEntryAssembly().Location)).Length > 1)
            {
                return;  // すでに起動している場合は終了
            }

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}


```

:::note info
**まさかのWinForms**
WinFormsでスクリーンセーバーを作るというのが気になったので調べた結果、VB6の頃からWinFormsで作成していた記事を発見（衝撃）

プロパティの設定等が異なるものの、おおよその仕組みが備わっている事に驚く。
:::


https://www5b.biglobe.ne.jp/~kouta_y/news/newsvb/vb02.html

## 出力プログラムの修正
ほぼコピペで動くものの、ある程度読み替えが必要な箇所がある。

#### Main()
出力プログラムにはFormの処理にMain()がくっついているので、Main()で書かれている多重起動についてはProgram.csのMain()へコピーする。

#### 実行すると即終了してしまうプログラムの修正
このままデバッグ実行すると、マウスを動かしてないのにOnMouseMove()イベントが発生し即時終了してしまう事から若干余裕を持たせる。

```OnMouseMoveの修正.cs
private Point lastMousePosition = new Point();

protected override void OnMouseMove(MouseEventArgs e)
{
    if (lastMousePosition.IsEmpty)
    {
        lastMousePosition = e.Location;
        return;
    }

    if (Math.Abs(e.X - lastMousePosition.X) > 5 || Math.Abs(e.Y - lastMousePosition.Y) > 5)
    {
        Application.Exit();
    }
}

```

## 実際にできたスクリーンセーバー
本家のStarfield Simulationとはかなり違い、黒い背景に白い点がランダム表示されるスクリーンセーバーができた。

**※ このスクリーンセーバーの良さを知ってたんじゃなかったのか・・・**

だがしかし、実際に作ってほしいものと異なるものの、スクリーンセーバーを作成できた事には変わりない。やりたい事ができた達成感は言葉では表しきれないものがある。

![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119455/77f5ac57-868c-443c-27af-d9e3bd46c1b5.gif)

## スクリーンセーバーを登録する
ビルドはexeとして出力されるため、拡張子をscrに変更する事で登録することができた。

# 感想
スクリーンセーバーは思いのほか簡単に作れる。

キーボード、マウス入力による処理終了プログラムという事を知ったので、例えば、マウス操作のみ有効にする事でゲームっぽい要素や特殊なスクリーンセーバーの解除等が工夫次第で出来るのではないか、というスクリーンセーバーに新たなる可能性を見出した気がする。

社内専用スクリーンセーバーを作成し、会社、組織に失われた個性を呼び覚ます手段として是非定着してほしい。