SPARKCREATIVE Tech Blog

https://www.spark-creative.jp/

今だからこそのデスクトップマスコット入門

はじめまして、クライアントエンジニアの細井です。

初投稿は趣味と実益を兼ねてデスクトップマスコットについてです!

~お品書き~

デスクトップマスコットって?

皆さんはデスクトップマスコットというものをご存じでしょうか?

2000年代の初め頃そこそこ流行ったソフトウェアなのですが、
デスクトップ上に常駐し、簡単なおしゃべりが出来たり検索が出来たり結構便利なソフトでした。
Officeのイルカをイメージしてもらえればいいかと思います。
(今の子達はもう知らないんですかね・・・?) 

そんなデスクトップマスコットですが、当時のPCスペックでは処理が鈍くなったりと問題がありました。
ですが今のスペックなら重くて捗らないという事はほとんどないでしょう。 
さらに!昨今Vtuberが流行っていますね?"推し"いますよね?推しをずっと眺めていたいですよね!?
後は言わなくてもわかりますね? 

環境

今回はWndows Formアプリケーションを作成してみます。
・Visual Studio2017 

作成

プロジェクトの作成

f:id:spark-hosoi-koyo:20201231085152p:plain
 Windows フォームアプリケーション(.NET Framework)を選択
※Visual C#の欄がないよって方は設定/アプリと機能でVSを検索し「変更」ボタンからインストールしてください。
エディターが起動しForm1.cs[デザイン]が表示されていれば成功です。

DXライブラリ導入

MMDモデルを描画するのにDXライブラリを使用します。
公式サイトからVisual C#用のパッケージをダウンロードしてください。
ダウンロード後、ファイルを解凍してください。
f:id:spark-hosoi-koyo:20201231091258p:plain
枠内の3ファイルを作成したプロジェクト内のexeファイルと同じ階層に移動します。
 デフォルトだと「プロジェクト名\bin\Debug」が作成されていると思うのでそこに移動してください。
Releaseビルドをした際は「プロジェクト名\bin\Release」が作成されるので、その際は
そこにもファイルを追加してください。
その後VisualStadioの「プロジェクト(P)/参照の追加」を選択し参照マネージャーを起動します。
f:id:spark-hosoi-koyo:20201231093518p:plain
「参照(B)」を選択し先程追加した「DxLibDotNet.dll」を選択してください。
これでDXライブラリを使用することが出来ます。

DXライブラリセットアップ~ウィンドウ表示

 さてここからはコーディングになりますが、
最低限のコード量はそれほど多くないのでノンプログラマーでも大丈夫です!
頑張りましょう!
まずはフォームの処理を記述するためにForm1.csを開きます。
f:id:spark-hosoi-koyo:20201231100805p:plain
デフォルトでは下記になっていると思います。

using省略
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

DXライブラリ使用のための基本的な関数を追加します。

using省略
// DXライブラリのusing追加.
using DxLibDLL;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // DXライブラリの初期設定全般.
            // Log.txtを生成しないように設定.
            DX.SetOutApplicationLogValidFlag(DX.FALSE);
            // DXライブラリの親ウインドウをこのフォームに設定.
            DX.SetUserWindow(Handle);
            // Zバッファの深度を24bitに変更.
            DX.SetZBufferBitDepth(24);
            // 裏画面のZバッファの深度を24bitに変更.
            DX.SetCreateDrawValidGraphZBufferBitDepth(24);
            // 画面のフルスクリーンアンチエイリアスモードの設定をする.
            DX.SetFullSceneAntiAliasingMode(4, 2);
            // DXライブラリの初期化処理.
            DX.DxLib_Init();
            // 描画先を裏画面に設定.
            DX.SetDrawScreen(DX.DX_SCREEN_BACK);
        }
        /// <summary>
        /// この関数をメインループとする.
        /// </summary>
        public void MainLoop()
        {
            // 裏画面を消す.
            DX.ClearDrawScreen();
            // 裏画面を表画面にコピー.
            DX.ScreenFlip();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            // DxLibの終了処理.
            DX.DxLib_End();
        }
    }
}

関数の機能等については本記事の内容から外れてしまうため解説は致しません。
初期化処理の他に2つ関数を追加しましたが後程解説します。

続いてProgram.csを開いてみましょう。
ここがこのプロジェクトのメイン部となります。
少々変更したものが下記になります。

using省略
namespace WindowsFormsApp1
{
    static class Program
    {
        /// <summary>
        /// アプリケーションのエントリポイント.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Form1 form = new Form1();
            form.Show();

            // 自分で作成したループを使用.
            while(form.Created) {
                form.MainLoop();
                Application.DoEvents();
            }
        }
    }
}

メインループを自分で管理するように変更してみました。
form.MainLoop()は先程Form1.csに追加した関数になります。
この関数内に書かれている処理がアプリ終了までグルグル回り続けます。

Windows フォームアプリケーションではプロパティからイベント(ボタンが押された時など)を登録する事が出来ます。
先程作成したForm1_FormClosed(object sender, FormClosedEventArgs e)関数を登録してみましょう。
「Form1.cs[デザイン]」を開き、プロパティの雷アイコンを選択し「動作/FormClosed」の欄を探して右のエリアをクリックします。
プルダウンに先程のForm1_FormClosedがあると思うので選択しましょう。
f:id:spark-hosoi-koyo:20201231105901p:plain
※プロパティが無い場合はVisualStudio上部の「表示/プロパティウィンドウ」をクリックしましょう。

ここまで出来たら一旦実行してみましょう!
f:id:spark-hosoi-koyo:20201231103609p:plain
こんな感じの真っ暗なウィンドウが表示されれば成功です。

モデルの表示

いよいよモデルを表示していきます!
今回はどっと式初音ミクV3ver.2.00をお借りします。
ダウンロード後解凍し、フォルダ内のどっと式初音ミク_V3.pmxファイルとtexフォルダをコピー、
exeファイルと同じ階層にModelフォルダを作成しペーストしましょう。
.pmxファイルの名前は後々のことを考えてModel.pmxとでもしておきます。
Form1.csを編集していきます。

using省略
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private int _model_handle;

        public Form1()
        {
            InitializeComponent();

            // DXライブラリの初期設定全般.
            // Log.txtを生成しないように設定.
            DX.SetOutApplicationLogValidFlag(DX.FALSE);
            // DXライブラリの親ウインドウをこのフォームに設定.
            DX.SetUserWindow(Handle);
            // Zバッファの深度を24bitに変更.
            DX.SetZBufferBitDepth(24);
            // 裏画面のZバッファの深度を24bitに変更.
            DX.SetCreateDrawValidGraphZBufferBitDepth(24);
            // 画面のフルスクリーンアンチエイリアスモードの設定をする.
            DX.SetFullSceneAntiAliasingMode(4, 2);
            // DXライブラリの初期化処理.
            DX.DxLib_Init();
            // 描画先を裏画面に設定.
            DX.SetDrawScreen(DX.DX_SCREEN_BACK);

            // 3Dモデルの読み込み.
            this._model_handle = DX.MV1LoadModel("Model/Model.pmx");

            // カメラの設定.
            // 奥行0.1~1000をカメラの描画範囲とする.
            DX.SetCameraNearFar(0.1f, 1000.0f);
            // 第1引数の位置から第2引数の位置を見る角度にカメラを設置.
            DX.SetCameraPositionAndTarget_UpVecY(DX.VGet(0.0f, 10.0f, -20.0f), DX.VGet(0.0f, 10.0f, 0.0f));
        }

        /// <summary>
        /// この関数をメインループとする.
        /// </summary>
        public void MainLoop()
        {
            // 裏画面を消す.
            DX.ClearDrawScreen();
            // 3Dモデルの描画.
            DX.MV1DrawModel(this._model_handle);
            // 裏画面を表画面にコピー.
            DX.ScreenFlip();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            // DxLibの終了処理.
            DX.DxLib_End();
        }
    }
}

DX.MV1LoadModel("Model/Model.pmd");この関数でモデルの読み込みを行っています。
()内のパスは先ほど追加したモデルのパスを指定しましょう。
DX.SetCameraNearFar(0.1f, 1000.0f);
DX.SetCameraPositionAndTarget_UpVecY(DX.VGet(0.0f, 10.0f, -20.0f), DX.VGet(0.0f, 10.0f, 0.0f));
上記二つの関数はカメラの設定になります。
説明は長くなるので省きますが、位置や注視点を設定出来ます。
実際に表示してるのは下記の関数になります。
DX.MV1DrawModel(this._model_handle);

さてこれでモデルが表示出来ているはずです!
f:id:spark-hosoi-koyo:20201231145114p:plain

背景の透過

枠の中に居たままでは可愛くないので透過しましょう。

using省略
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private int _model_handle;

        public Form1()
        {
            InitializeComponent();

            // 画面サイズの設定(全画面).
            ClientSize = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);

            // DXライブラリの初期設定全般.
            // Log.txtを生成しないように設定.
            DX.SetOutApplicationLogValidFlag(DX.FALSE);
            // DXライブラリの親ウインドウをこのフォームに設定.
            DX.SetUserWindow(Handle);
            // Zバッファの深度を24bitに変更.
            DX.SetZBufferBitDepth(24);
            // 裏画面のZバッファの深度を24bitに変更.
            DX.SetCreateDrawValidGraphZBufferBitDepth(24);
            // 画面のフルスクリーンアンチエイリアスモードの設定をする.
            DX.SetFullSceneAntiAliasingMode(4, 2);
            // DXライブラリの初期化処理.
            DX.DxLib_Init();
            // 描画先を裏画面に設定.
            DX.SetDrawScreen(DX.DX_SCREEN_BACK);

            // 3Dモデルの読み込み.
            this._model_handle = DX.MV1LoadModel("Model/Model.pmx");

            // カメラの設定.
            // 奥行0.1~1000をカメラの描画範囲とする.
            DX.SetCameraNearFar(0.1f, 1000.0f);
            // 第1引数の位置から第2引数の位置を見る角度にカメラを設置.
            DX.SetCameraPositionAndTarget_UpVecY(DX.VGet(0.0f, 10.0f, -20.0f), DX.VGet(0.0f, 10.0f, 0.0f));
        }

        /// <summary>
        /// この関数をメインループとする.
        /// </summary>
        public void MainLoop()
        {
            // 裏画面を消す.
            DX.ClearDrawScreen();
            // 背景を設定(透過させる).
            DX.DrawBox(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, DX.GetColor(1, 1, 1), DX.TRUE);

            // 3Dモデルの描画.
            DX.MV1DrawModel(this._model_handle);
            // 裏画面を表画面にコピー.
            DX.ScreenFlip();

            // 枠がなくなるのでESCキーで終了できるよ設定.
            if(DX.CheckHitKey(DX.KEY_INPUT_ESCAPE) != 0) {
                Close();
            }
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            // DxLibの終了処理.
            DX.DxLib_End();
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            // フォームの枠を非表示にする.
            FormBorderStyle = FormBorderStyle.None;
            // 透過色を設定.
            TransparencyKey = Color.FromArgb(1, 1, 1);
        }
    }
}

追加した下記の関数をプロパティに追加します。
private void Form1_Shown(object sender, EventArgs e)
f:id:spark-hosoi-koyo:20201231124854p:plain

さー実行してみましょう!
f:id:spark-hosoi-koyo:20201231145121p:plain
表示出来たでしょうか?
現在は全画面に設定されていますが、
ClientSize = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
この部分を変更することでサイズを変更することができます。

今のままだと別のウィンドウを押した際モデルが裏に行ってしまいます。
モデルには常に全面にいて欲しいので少し設定します。
f:id:spark-hosoi-koyo:20201231131119p:plain
プロパティのTopMostの設定をTrueに変更します。
これで最前面に固定できます。

モーションを再生してみる

ここからはおまけみたいなものですが、
モーションを再生できると可愛さマシマシです。

ぴょこぴょこミクさんをお借りいたします。
フォルダ内の1840フレーム.vmdを使用します。
上記モーションファイルをモデルと同じフォルダにコピペしてください。
その後ファイル名を(モデル名)000.vmdと変更しておいてください。
例)Model000.vmd
コードも追加します。

using初期化
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private int _model_handle;

        private int _attach_index;
        private float _total_time;
        private float _play_time = 0.0f;
        private float _play_speed = 0.4f;
        private int _motion_id = 0;

        public Form1()
        {
            InitializeComponent();

            // 画面サイズの設定.
            ClientSize = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);

            DXライブラリの初期設定全般省略

            // 3Dモデルの読み込み.
            this._model_handle = DX.MV1LoadModel("Model/Model.pmx");

            // モーションの選択(モデル名+数字のアニメーションが自動で読み込まれる?).
            this._attach_index = DX.MV1AttachAnim(this._model_handle, this._motion_id, -1, DX.FALSE);
            // モーションの総再生時間を取得.
            this._total_time = DX.MV1GetAttachAnimTotalTime(this._model_handle, this._attach_index);

            // カメラの設定.
            // 奥行0.1~1000をカメラの描画範囲とする.
            DX.SetCameraNearFar(0.1f, 1000.0f);
            // 第1引数の位置から第2引数の位置を見る角度にカメラを設置.
            DX.SetCameraPositionAndTarget_UpVecY(DX.VGet(0.0f, 10.0f, -20.0f), DX.VGet(0.0f, 10.0f, 0.0f));
        }

        /// <summary>
        /// この関数をメインループとする.
        /// </summary>
        public void MainLoop()
        {
            // 裏画面を消す.
            DX.ClearDrawScreen();
            // 背景を設定(透過させる).
            DX.DrawBox(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, DX.GetColor(1, 1, 1), DX.TRUE);

            // 時間を進める.
            this._play_time += _play_speed;
            // モーションの再生位置が終端まで来たら最初に戻す.
            if(this._play_time >= this._total_time) {
                this._play_time = 0.0f;
            }
            // モーションの再生位置を設定.
            DX.MV1SetAttachAnimTime(this._model_handle, this._attach_index, this._play_time);

            // 3Dモデルの描画.
            DX.MV1DrawModel(this._model_handle);
            // 裏画面を表画面にコピー.
            DX.ScreenFlip();

            // 枠がなくなるのでESCキーで終了できるよ設定.
            if(DX.CheckHitKey(DX.KEY_INPUT_ESCAPE) != 0) {
                Close();
            }
        }
    }
}

this._attach_index = DX.MV1AttachAnim(this._model_handle, this._motion_id, -1, DX.FALSE);
_motion_idに追加したモーションの番号(Model000.vmdの000の部分)を指定します。
f:id:spark-hosoi-koyo:20201231152254g:plain
動いていれば成功です!
※モーションによってはカメラの範囲からはみ出してしまうものもあるので、その際はカメラを調整してあげてください。

おわりに

ここで作成したものはあくまで最低限の見た目のみです。
機能を追加したりモーションを自分で作ったりと拡張性は無限大です。
是非自分好みにカスタマイズしてみてください!