うら干物書き

ゲームを作っています。

【Unity】マウスのある場所にオブジェクトを配置したい

 一週間ゲームジャムが始まったようです。

 もちろん参加しますとも。

 どんなゲームを作るのかは平日の間になんとなく考えついたんだんですが、ちゃんとゲームになるだろうか……。

 プロジェクトを立ち上げる前に、ゲームに使う技術の検証をしておきましょう。
 今回検証するのは、マウスポインタのある位置に3D空間のオブジェクトを移動させるという、ググれば山ほど出てくる技術の検証です。

f:id:urahimono:20170429075208p:plain


この記事にはUnity5.6.0f3を使用しています。

 マウスの座標を3D空間のワールド座標に変換してみましょう。

 マウスの座標はInput.mousePositionで取得できます。
 そして、ゲームで使用しているカメラのScreenToWorldPoint()を使用して、ワールド座標に変換します。
 カメラの行列を使って云々計算しなくても、関数呼出し一発で出来るので、学のない僕にだってできちゃいます。

docs.unity3d.com

using UnityEngine;

public class TestComponent : MonoBehaviour
{
    [SerializeField]
    private GameObject  m_object    = null;

    private void Update()
    {
        Vector2 touchScreenPosition = Input.mousePosition;
        Camera  gameCamera          = Camera.main;
        Vector3 touchWorldPosition  = gameCamera.ScreenToWorldPoint( touchScreenPosition );

        m_object.transform.position = touchWorldPosition;
    }

} // class TestComponent

f:id:urahimono:20170429074909p:plain

 ゲーム画面に表示されてない!
 やはり学のない僕には無理なのか……。

 シーンビューも確認してみましょう。
f:id:urahimono:20170429074945p:plain

 あった!
 カメラにぴったりくっついている。
 
 なぜこうなるのでしょうか。コードを確認してみましょう。
 ああ。なるほど。ScreenToWorldPoint()に渡す座標のZ値に何もいれてない(0.0f)からですね。
 奥行の値が0.0fなら、そりゃカメラにくっつくさ。
 とりあえず適当に10.0fぐらいを設定してみましょうか。

private void Update()
{
    Vector3 touchScreenPosition = Input.mousePosition;

    // 10.0fに深い意味は無い。画面に表示したいので適当な値を入れてカメラから離そうとしているだけ.
    touchScreenPosition.z       = 10.0f;

    Camera  gameCamera          = Camera.main;
    Vector3 touchWorldPosition  = gameCamera.ScreenToWorldPoint( touchScreenPosition );

    m_object.transform.position = touchWorldPosition;
}

f:id:urahimono:20170429075014g:plain

 わーい出た。
 ただ、シーンビューを見る限り、ゲーム画面外にマウスの座標まで取得していますね。
 あまりよろしくないので、一応クランプしておきます。

private void Update()
{
    Vector3 touchScreenPosition = Input.mousePosition;

    touchScreenPosition.x       = Mathf.Clamp( touchScreenPosition.x, 0.0f, Screen.width );
    touchScreenPosition.y       = Mathf.Clamp( touchScreenPosition.y, 0.0f, Screen.height );

    // 10.0fに深い意味は無い。画面に表示したいので適当な値を入れてカメラから離そうとしているだけ.
    touchScreenPosition.z       = 10.0f;

    Camera  gameCamera          = Camera.main;
    Vector3 touchWorldPosition  = gameCamera.ScreenToWorldPoint( touchScreenPosition );

    m_object.transform.position = touchWorldPosition;
}

f:id:urahimono:20170429075047g:plain

 画面外にはみでなくなりました。

 さて、マウス座標のある場所にオブジェクトを設置することができました。
 ただ、実際ゲーム上でこの機能を使う場合は、床オブジェクトの上に設置するように作りたいのです。
 少し改良を加えていきましょう。

 では、床オブジェクトを作成します。

f:id:urahimono:20170429075125p:plain

 この床の上に球オブジェクトが乗っている状態にしたいです。
 無論、マウスポインターのある位置に。

 どうしよう……。
 そうだ、レイを使おう!

 カメラには、ScreenToWorldPoint()だけでなく、レイを飛ばすScreenPointToRay()もあります。
 便利な世の中になったものです。

docs.unity3d.com

private void Update()
{
    Vector2 touchScreenPosition = Input.mousePosition;

    touchScreenPosition.x   = Mathf.Clamp( touchScreenPosition.x, 0.0f, Screen.width );
    touchScreenPosition.y   = Mathf.Clamp( touchScreenPosition.y, 0.0f, Screen.height );

    Camera  gameCamera      = Camera.main;
    Ray     touchPointToRay = gameCamera.ScreenPointToRay( touchScreenPosition );

    // デバッグ機能を利用して、スクリーンビューでレイが出ているか見てみよう。
    Debug.DrawRay( touchPointToRay.origin, touchPointToRay.direction * 1000.0f );
}

f:id:urahimono:20170429075208p:plain

 すごい、かっこいい!
 まあ実際のゲーム画面ではレイは見えませんけどね。

 このレイと当たり判定を取ります。
 まず、床オブジェクトにコリジョンが付いていることを確認しましょう。

f:id:urahimono:20170429075247p:plain

 コリジョンが付いているようなので、Physics.Raycast()を使って当たり判定を取ります。

docs.unity3d.com

docs.unity3d.com

private void Update()
{
    Vector2 touchScreenPosition = Input.mousePosition;

    touchScreenPosition.x   = Mathf.Clamp( touchScreenPosition.x, 0.0f, Screen.width );
    touchScreenPosition.y   = Mathf.Clamp( touchScreenPosition.y, 0.0f, Screen.height );

    Camera  gameCamera      = Camera.main;
    Ray     touchPointToRay = gameCamera.ScreenPointToRay( touchScreenPosition );

    RaycastHit hitInfo = new RaycastHit();
    if( Physics.Raycast( touchPointToRay, out hitInfo ) )
    {
        m_object.transform.position = hitInfo.point;
    }

    // デバッグ機能を利用して、スクリーンビューでレイが出ているか見てみよう。
    Debug.DrawRay( touchPointToRay.origin, touchPointToRay.direction * 1000.0f );
}

f:id:urahimono:20170429075343g:plain

 思ってたのと違う!
 なにこれ、何が起きたの。
 球のオブジェクトのプロパティを見てみる。

f:id:urahimono:20170429075418p:plain

 お前もコリジョンもっとるのかい!
 球もColliderを持っているのでそれにレイが当たってしまうようですね。
 Colliderを外してもいいんですが、レイヤーを変えることで対応しましょう。
 Physics.Raycast()に当たるレイヤーを指定することが出来ます。
 ただ、わざわざレイヤーを追加するもの面倒くさいです。
 そんな場合は、Unity側でレイとの当たり判定を行わないレイヤーが既に用意されています。
 それがIgone Raycastです。
 これを使いましょう。

f:id:urahimono:20170429075436p:plain
f:id:urahimono:20170429075442g:plain

 上手くいったようですね。
 これで今回のゲームジャム用に考えていたアイディアが生かせるゲームが作れそうです。