Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

Unity: デスクトップマスコットで癒されたい

ライセンス表記

ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています

まえがき

BPSの協力会社として横浜を拠点に活動しております、最近記事のスタイルを模索中の株式会社ECNのFuseです。
突然ですが、みなさんはUnity、使ってますか?今日はあまりにも癒しが足りないので、
みなさんご存じUnityちゃんを撫でられるデスクトップマスコットで癒されたいと思います!

目次

  1. まえがき
  2. まずはUnityちゃんをウィンドウに召還する
  3. ウィンドウを透過したい
  4. ドラッグで動かしたい&常に最前面に表示したい
  5. 撫でを検知して笑顔になってほしい
  6. あとがき
  7. 参考記事

まずはUnityちゃんをウィンドウに召還する

というわけで早速召還してみました。Cube3つで適当な部屋を作り、
ユニティちゃん公式サイトにて配布されているSDユニティちゃんを召還!かわいいですね。
ですがこのままでは背景やウィンドウのタイトルバーがそのままでデスクトップマスコットっぽくないですね。

ウィンドウを透過したい

というわけで改良しました。

背景を透過するためのクラス「WindowTrans」を作成しました。

// WindowTrans.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.InteropServices;
public class WindowTrans : MonoBehaviour//ウィンドウの背景を透過する
{
    #region WINDOWS API

    [DllImport("User32.dll")]
    private static extern IntPtr GetActiveWindow();
    [DllImport("user32.dll", EntryPoint = "GetWindowLong")]
    private static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
    [DllImport("User32.dll")]
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);

    [DllImport("user32.dll", EntryPoint = "SetLayeredWindowAttributes")]
    private static extern Boolean SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

    #endregion
    const int WS_BORDER = 0x00800000; // 境界線を持つウィンドウ
    const int WS_DLGFRAME = 0x00400000; // ダイアログボックスのスタイルの境界を持つウィンドウ
    const int WS_CAPTION = WS_BORDER | WS_DLGFRAME; // タイトルバーを持つウィンドウ
    private IntPtr window;//デスクトップマスコットのウィンドウ

    private void Awake() {
        window=GetActiveWindow();//ウィンドウを取得「
        SetWindowLong(window,-20,0x80000);
        uint style = GetWindowLong(window, -16); // ウィンドウの情報を取得
        SetWindowLong(window, -16, style ^ WS_CAPTION); // ウィンドウのタイトルバーを消す
        SetLayeredWindowAttributes(window,0xFF00FF,0,1);//ウィンドウの透過色を指定
    }
}

実際の背景はこのようにマゼンタ1色になっており、WindowsAPIの機能を使ってマゼンタの部分を透過しています。
これにより、Unityでも前述の写真のような背景が透明なウィンドウを作ることができるのです。
ですがこのままだとほかのウィンドウをアクティブ化するとUnityちゃんが後ろに隠れてしまいます。
作業片手に撫でられるように常に最前面に表示し、なおかつ邪魔にならないようにドラッグで動かしたいところなのですが…

ドラッグで動かしたい&常に最前面に表示したい

というわけで改良しました。
ウィンドウを動かせるようにし、なおかつ常に最前面で表示するクラス「WindowDragger」を作成しました。

// WindowDragger.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.InteropServices;
public class WindowDragger : MonoBehaviour
{
    [DllImport("User32.dll")]
    private static extern int SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
    [DllImport("User32.dll")]
    static extern bool GetCursorPos(out POINT lppoint);
    [DllImport("User32.dll")]
    private static extern IntPtr GetActiveWindow();
        [StructLayout(LayoutKind.Sequential)]
    struct POINT
    {
        public int X { get; set; }
        public int Y { get; set; }

    }
    private IntPtr window;
    private POINT p;
    Vector3 beforepos;
    private void Awake() {
        GetCursorPos(out p);
        window=GetActiveWindow();
    }

    // Update is called once per frame
    void Update()
    {
        var delta=(beforepos-Input.mousePosition).magnitude;
        beforepos=Input.mousePosition;
        Debug.Log(delta);
        if(Input.GetMouseButton(0)&&(delta>1)){//左クリックしながらマウスを動かしたなら
            GetCursorPos(out p);//マウスの位置を取得

        }
        SetWindowPos(window,-1,p.X-240,p.Y-360,480,720,40);//中心の位置がマウスになるように移動
    }
}

起動時とドラッグされた時に変数pにカーソルの位置を入れ、
そこに中心が来るようにフレームSetWindowPosで動きます。

SetWindowPosのドキュメントにあるように、第二引数に-1を指定することで、ウィンドウを一番手前に動かせます。

これを毎フレーム実行すれば、常にユニティちゃんが手前に来るわけです。
ですがこのままではお互い何も干渉できません。隔絶されてます。
なのでマウスで頭を撫でられる機能を実装しましょう!

撫でを検知して笑顔になってほしい

というわけで実装しました。

// NadenadeSensor.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NadenadeSensor : MonoBehaviour
{
    Vector3 beforepos;
    int nadedir=0;//最初の撫では左右どちらでもOK 
    [SerializeField] Animator anm;
    // Update is called once per frame
    void Update()
    {
        var delta=(beforepos-Input.mousePosition);
        beforepos=Input.mousePosition;
        Ray r=Camera.main.ScreenPointToRay(Input.mousePosition);//マウスの座標とカメラからレイを生成
        Physics.Raycast(r,out var result);//レイキャスト
        if(result.collider.gameObject==this.gameObject){//例が頭のコライダーにあたってたら
            if(Mathf.Abs(delta.x)>5&&nadedir!=Mathf.Sign(delta.x)){//マウスがそれなりの速度でさっきの撫でと逆に動いてたら
                nadedir=(int)Mathf.Sign(delta.x);//撫での向きを記録
                anm.SetTrigger("nade");//撫でを通知
            }
        }else{
            nadedir=0;//撫での向きを初期化
        }
    }
}

頭を撫でられたことを検知し、Animatorに通知するクラス「NadenadeSensor」を作成しました。
頭のあたりを撫でられたことを検知し、Animatorコンポーネントに通知します。
ユニティちゃんの頭にコライダーと一緒にくっつけます。

その後ユニティちゃんのAnimatorにトリガー"nade"を追加、撫でられたら笑顔に、
一定時間撫でがないと通常の表情に戻るようにします。

ユニティちゃんのデスクトップマスコット、これにて完成です!

あとがき

というわけで今日はたった3つのスクリプトでかわいいデスクトップマスコットを作りました。
32ビットの時代からあるWindowsAPIですが、使いこなせば様々なものが作れます。
今回のようなマスコットから第四の壁を破るメタホラーまで、UnityとWindowsAPIの組み合わせ、
皆さんもぜひ試してみてはいかがでしょうか?

参考記事


株式会社ECNはPHP、JavaScriptを中心にお客様のご要望に合わせたwebサービス、システム開発を承っております。
ビジネスの最初から最後までサポートを行い
お客様のイメージに合わせたWebサービス、システム開発、デザインを行います。



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。