強火で進め

このブログではプログラム関連の記事を中心に書いてます。こちらで( http://blog.livedoor.jp/tsuyobi-outdoor/ ) アウトドア関連の記事も書いてます。

マウスの位置にオブジェクトを移動させる


Facebookの「Unityユーザー助け合い所」で「タッチした位置にオブジェクトを移動するにはどうすれば?」という質問が挙っていました。

この処理って意外と難しい様な気がしたので自分でも作ってみる事にしました。質問ではタッチ(iPhoneAndroid向け)の話でしたがテストがメンドクサイのでマウスの位置で実装しました。マウスの位置で指定している部分をタッチの場合にはタッチのデータに置き換えるだけでiPhoneAndroidでも動作するかと思います。

ソースコードこちらで配布しています。

仕組みの概要

今回のお題、背景に地面など何らかのオブジェクトが有る場合には簡単に作成出来ますが何も無い場合には意外と実装がメンドクサイのでは?と思いました。

ゴリゴリ計算すればもちろんできるでしょうが折角Unity使っているのだし簡単にやりたい所です。

と言う事で今回はカメラの前にパネルを一枚引いてそのパネルとのHitテストで実装する事にしました。

作成手順

まずはPlaneを作成し、名前をHitPlaneにした後にカメラの正面に配置します。HitPlaneがカメラの視界を覆う様に充分な大きさにリサイズします。

マウスの位置に移動したオブジェクトが見やすい様にHitPlaneのマテリアルに色を設定&半透明にします。

マウスの位置でのHitチェック時にHitPlane以外のオブジェクトとHitしない様にHitPlaneは専用のlayerを設定します。

次にプログラムです。JavaScriptファイルを作成し、以下の様なプログラムを記述します。

#pragma strict

var player: GameObject;
var hitPlane: GameObject;

function Start () {
	hitPlane.renderer.enabled = false;
}

function Update () {
    var hit : RaycastHit;
    var ray : Ray;
    var hitPlanePos = hitPlane.transform.position;
    hitPlane.transform.position = new Vector3(hitPlane.transform.position.x, hitPlane.transform.position.y, player.transform.position.z);
    ray = Camera.main.ScreenPointToRay (Input.mousePosition);
    var layerMask = 1 << 8;
    if (Physics.Raycast (ray, hit, Mathf.Infinity, layerMask)) {
		player.transform.position = new Vector3(hit.point.x, hit.point.y, player.transform.position.z);
    }
}
プログラムの解説
var player: GameObject;
var hitPlane: GameObject;

先頭でクリックした位置に移動させる対象である player と HitPlane の参照を保存するプロパティを準備します。
hitPlane には HitPlaneを設定、playerには移動させたい対象となるオブジェクトを設定して下さい。

function Start () {
	hitPlane.renderer.enabled = false;
}

スタート時にHitPlaneの描画は行わない様にします。あくまで、描画だけ行わない状態です。当たり判定(collision)についてはそのまま有効で有るためちゃんとHitチェックが行えます。

    ray = Camera.main.ScreenPointToRay (Input.mousePosition);

Camera.main.ScreenPointToRay() でマウスの位置をスクリーンでの位置(3Dワールドでの位置)に変更します。

    var layerMask = 1 << 8;
    if (Physics.Raycast (ray, hit, Mathf.Infinity, layerMask)) {

Physics.Raycast() ではマウスの位置でHitPlaneとHitした位置を求めています。

layerMaskでは 1 << 8 でHitPlaneに設定してあるlayerを指定しています。これは今回、HitPlaneに指定したlayerがユーザ定義Layerが8番目からだからです。もし、8番目以外を使用した場合にはその番号に変更して下さい。

		player.transform.position = new Vector3(hit.point.x, hit.point.y, player.transform.position.z);

Hitチェックで求められた位置にオブジェクト(player)を移動さます。マウスの位置のX,Y座標に移動させ、Z座標については変更しない様にしています。

以上な感じで出来たので「Unityユーザー助け合い所」に回答を付けようかと思ってみた所、もっとスマートな解決方法として camera.ScreenToWorldPoint() を使うという回答がついてました。おーっ、そっちの方が無茶苦茶簡単だよorz

まぁ、これで自分がこんな実装が必要になった時にはちゃんと camera.ScreenToWorldPoint() 使うだろうし、良しとしよう。

camera.ScreenToWorldPoint() で実装したコードはこちら