FC2ブログ
ヽ|∵|ゝ(Fantom) の 開発blog? ホーム »カメラスクリプト
このページの記事一覧

【Unity】【C#】SmoothFollow3(SmoothFollow に回転・遠近・高さ操作とピンチ・スワイプとの連携機能を付けた拡張版)  


 実は非常に好評だったりする「SmoothFollow2」の後継版。基本的には以前の内容と変わらないのだが、せっかく「ピンチ」「スワイプ」といった機能も作ったので、それらも連携できるようにしてみた。モバイル操作対応版という感じでもあるかな。ただ「SmoothFollow2」でもかなりパラメタ(フィールド)が多かったので、今回のバージョンからは少し機能ごとにまとめたため、変数名は変更した(機能はそのまま)。

SmoothFollow3PinchInput を利用してる


 自分で作るのが面倒な人はプラグイン内にデモとして使用しているので、ダウンロードしてコピペで利用して欲しい。


>>プラグイン&サンプルをダウンロード
(Google Drive を利用。画面右上にあるダウンロードアイコンを押す)



(※) Unity 5.6.3p1 / Windows10(x64) / Galaxy S7 Edge (Android 7.0) で確認



●SmoothFollow3(StanardAssets の SmoothFollow に回転・遠近・高さ操作とピンチ・スワイプとの連携機能を付けた拡張版)

using System;
using UnityEngine;

namespace FantomLib
{
/// <summary>
/// SmoothFollow に左右回転アングルと高さと距離の遠近機能を追加したもの ver.3
/// 2018/01/09 Fantom (Unity 5.6.3p1)
/// http://fantom1x.blog130.fc2.com/blog-entry-289.html
/// (SmoothFollow2 からの変更点)
/// http://fantom1x.blog130.fc2.com/blog-entry-163.html
///・SwipeInput のコールバックでのスワイプで一定角度の旋回を追加。
///・PinchInput のコールバックでのピンチで距離の操作を追加(モバイル用)。
///・起動時に設定された対象(target)から、距離(distance)、高さ(height)、角度(preAngle)を算出するオプションを追加。
///・初期状態へのリセットメソッド(ResetOperations())を追加。
///・ドラッグの認識する画面上の領域(validArea)を追加。
///・各設定をクラスで分けたので、変数名が変更された(機能は全て同じ)。
///(使い方)
///・カメラなどの GameObject にアタッチして、インスペクタから target に視点となるオブジェクトを登録すれば使用可。
///(仕様説明)
///・画面全体を(0,0)-(1,1)とし、有効領域内(Valid Area)でタッチまたはマウスでクリックしたとき認識する。
///・タッチ操作は指1本のみ(かつ最初の1本)の操作が有効となる(2本以上→1本になったときは認識しない)。
///・指でのドラッグとスワイプ操作を分けるため、AngleOperation.dragWidthLimit の値(画面幅による比率)より大きいときは(=指を素早く動かしたときは)ドラッグとして認識しない
/// (スワイプは SwipeInput.validWidth の値で認識)。
///・タッチデバイスを UNITY_ANDROID, UNITY_IOS としているので、他のデバイスも加えたい場合は #if の条件文にデバイスを追加する(Input.touchCount が取得できるもののみ)。
/// </summary>
public class SmoothFollow3 : MonoBehaviour
{
public Transform target; //追従するオブジェクト

public bool autoInitOnPlay = true; //distance, height, preAngle を起動時に target 位置から自動算出する
public float distance = 2.0f; //XZ平面の距離
public float height = 0f; //Y軸の高さ
public float preAngle = 0f; //カメラアングル初期値

public bool widthReference = true; //画面幅(Screen.width)サイズを比率の基準にする(false=高さ(Screen.height)を基準)

//認識する画面上の領域
public Rect validArea = new Rect(0, 0, 1, 1); //認識する画面領域(0.0~1.0)[(0,0):画面左下, (1,1):画面右上]


//回転操作
[Serializable]
public class AngleOperation
{
public float damping = 3.0f; //左右回転のスムーズ移動速度

//キー入力
public bool keyEnable = true; //回転のキー操作の ON/OFF
public float keySpeed = 45f; //左右回転速度
public KeyCode keyLeft = KeyCode.Z; //左回転キー
public KeyCode keyRight = KeyCode.X; //右回転キー

//ドラッグ
public bool dragEnable = true; //回転のドラッグ操作の ON/OFF
public float dragSpeed = 10f; //ドラッグ操作での回転速度
public float dragWidthLimit = 0.1f; //ドラッグとして認識できる幅(0 のとき制限なし ~ 1 のとき画面幅)。この幅以上は認識しない(スワイプと区別するため)。
}
public AngleOperation angleOperation;


//旋回(一定角度回転)
[Serializable]
public class TurnOperation
{
public float angle = 90f; //旋回の角度

//キー入力
public bool keyEnable = true; //旋回キーの ON/OFF
public KeyCode keyLeft = KeyCode.KeypadMinus; //左旋回キー
public KeyCode keyRight = KeyCode.KeypadPlus; //右旋回キー

//スワイプ
public bool swipeEnable = true; //スワイプで旋回の ON/OFF
}
public TurnOperation turnOperation;


//高さの操作
[Serializable]
public class HeightOperation
{
public float damping = 2.0f; //上下高さのスムーズ移動速度

//キー入力
public bool keyEnable = true; //高さのキー操作の ON/OFF
public float keySpeed = 1.5f; //キー操作での移動速度
public KeyCode keyUp = KeyCode.C; //高さ上へキー
public KeyCode keyDown = KeyCode.V; //高さ下へキー

//ドラッグ
public bool dragEnable = true; //高さのドラッグ操作での ON/OFF
public float dragSpeed = 0.5f; //ドラッグ操作での高さ移動速度
}
public HeightOperation heightOperation;


//距離の操作
[Serializable]
public class DistanceOperation
{
public float damping = 1.0f; //距離のスムーズ移動速度(キーとホイール)
public float min = 1.0f; //XZ平面での最小距離

//キー入力
public bool keyEnable = true; //距離のキー操作の ON/OFF
public float keySpeed = 0.5f; //距離の移動速度
public KeyCode keyNear = KeyCode.B; //近くへキー
public KeyCode keyFar = KeyCode.N; //遠くへキー

//ホイール
public bool wheelEnable = true; //距離のホイール操作の ON/OFF
public float wheelSpeed = 7f; //ホイール1目盛りの速度

//ピンチ
public bool pinchEnable = true; //ピンチで距離を操作する
public float pinchDamping = 5f; //ピンチでの距離のスムーズ移動速度(キーとホイールでの操作と分けるため)
public float pinchSpeed = 40f; //ピンチでの距離の変化速度
}
public DistanceOperation distanceOperation;


//初期状態リセット操作
[Serializable]
public class ResetOperation
{
public bool keyEnable = true; //初期状態リセットキーの ON/OFF
public KeyCode key = KeyCode.KeypadPeriod; //初期状態リセットキー
}
public ResetOperation resetOperation;


//Local Values
float angle; //カメラアングル(XZ平面)
Vector3 startPos; //マウス移動始点
float wantedDistance; //変化先距離
float resetDistance; //初期距離保存用
float resetHeight; //初期位置高さ保存用
bool pinched = false; //ピンチで操作したフラグ(distanceDamping と pinchDistanceDamping を切り替える)
bool dragging = false; //ドラッグの操作中フラグ


// Use this for initialization
void Start()
{
if (autoInitOnPlay && target != null)
{
height = transform.position.y - target.position.y;
Vector3 dir = Vector3.ProjectOnPlane(target.position - transform.position, Vector3.up);
distance = dir.magnitude;
preAngle = AngleXZWithSign(target.forward, dir);
}

angle = preAngle;
resetDistance = wantedDistance = distance;
resetHeight = height;
}

// Update is called once per frame
void Update()
{
#if !UNITY_EDITOR && (UNITY_ANDROID || UNITY_IOS) //タッチで取得したいプラットフォームのみ(モバイル等)
if (Input.touchCount != 1 || Input.touches[0].fingerId != 0) //最初の指1本の操作に限定する
{
dragging = false;
return;
}
#endif

//回転のキー操作
if (angleOperation.keyEnable)
{
if (Input.GetKey(angleOperation.keyLeft))
angle = Mathf.Repeat(angle + angleOperation.keySpeed * Time.deltaTime, 360f);

if (Input.GetKey(angleOperation.keyRight))
angle = Mathf.Repeat(angle - angleOperation.keySpeed * Time.deltaTime, 360f);
}

//旋回(一定角度回転)キー操作
if (turnOperation.keyEnable)
{
if (Input.GetKeyDown(turnOperation.keyLeft))
TurnLeft();

if (Input.GetKeyDown(turnOperation.keyRight))
TurnRight();
}

//高さのキー操作
if (heightOperation.keyEnable)
{
if (Input.GetKey(heightOperation.keyUp))
height += heightOperation.keySpeed * Time.deltaTime;

if (Input.GetKey(heightOperation.keyDown))
height -= heightOperation.keySpeed * Time.deltaTime;
}

//ドラッグ操作
if (angleOperation.dragEnable || heightOperation.dragEnable)
{
Vector3 movePos = Vector3.zero;

if (!dragging && Input.GetMouseButtonDown(0))
{
startPos = Input.mousePosition;
if (validArea.xMin * Screen.width <= startPos.x && startPos.x <= validArea.xMax * Screen.width &&
validArea.yMin * Screen.height <= startPos.y && startPos.y <= validArea.yMax * Screen.height)
{
dragging = true;
}
}
else if (dragging)
{
if (Input.GetMouseButton(0))
{
movePos = Input.mousePosition - startPos;
startPos = Input.mousePosition;

//ドラッグ幅で制限する(スワイプと分別するため)
if (angleOperation.dragWidthLimit > 0)
{
float limit = (widthReference ? Screen.width : Screen.height) * angleOperation.dragWidthLimit;
float d = Mathf.Max(Mathf.Abs(movePos.x), Mathf.Abs(movePos.y)); //大きい方で判定
if (d > limit)
{
movePos = Vector3.zero; //操作を無効にする
dragging = false;
}
}
}
else //Input.GetMouseButtonUp(0), exit
{
dragging = false;
}
}

if (movePos != Vector3.zero)
{
//回転のドラッグ操作
if (angleOperation.dragEnable)
angle = Mathf.Repeat(angle + movePos.x * angleOperation.dragSpeed * Time.deltaTime, 360f);

//高さのドラッグ操作
if (heightOperation.dragEnable)
height -= movePos.y * heightOperation.dragSpeed * Time.deltaTime;
}
}

//距離のキー操作
if (distanceOperation.keyEnable)
{
if (Input.GetKey(distanceOperation.keyNear))
{
wantedDistance = Mathf.Max(distanceOperation.min, distance - distanceOperation.keySpeed);
pinched = false;
}

if (Input.GetKey(distanceOperation.keyFar))
{
wantedDistance = distance + distanceOperation.keySpeed;
pinched = false;
}
}

//距離のホイール遠近
if (distanceOperation.wheelEnable)
{
float mw = Input.GetAxis("Mouse ScrollWheel");
if (mw != 0)
{
wantedDistance = Mathf.Max(distanceOperation.min, distance - mw * distanceOperation.wheelSpeed); //0.1 x N倍
pinched = false;
}
}

//初期状態リセット
if (resetOperation.keyEnable)
{
if (Input.GetKeyDown(resetOperation.key))
ResetOperations();
}
}

void LateUpdate()
{
if (target == null)
return;

//追従先位置
float wantedRotationAngle = target.eulerAngles.y + angle;
float wantedHeight = target.position.y + height;

//現在位置
float currentRotationAngle = transform.eulerAngles.y;
float currentHeight = transform.position.y;

//追従先へのスムーズ移動距離(方向)
currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, wantedRotationAngle,
angleOperation.damping * Time.deltaTime);
currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightOperation.damping * Time.deltaTime);
distance = Mathf.Lerp(distance, wantedDistance,
(pinched ? distanceOperation.pinchDamping : distanceOperation.damping) * Time.deltaTime);

//カメラの移動
var currentRotation = Quaternion.Euler(0, currentRotationAngle, 0);
Vector3 pos = target.position - currentRotation * Vector3.forward * distance;
pos.y = currentHeight;
transform.position = pos;

transform.LookAt(target);
}


//状態リセット(初期状態に戻す)
public void ResetOperations()
{
height = resetHeight;
distance = wantedDistance = resetDistance;
angle = preAngle;
}


//ピンチで距離を操作(モバイル等)
//http://fantom1x.blog130.fc2.com/blog-entry-288.html
//・PinchInput を使用して距離を操作する。
//width: ピンチ幅, delta: 直前のピンチ幅の差, ratio: ピンチ幅の開始時からの伸縮比(1:ピンチ開始時, 1以上拡大, 1より下(1/2,1/3,...)縮小)
public void OnPinch(float width, float delta, float ratio)
{
if (!distanceOperation.pinchEnable)
return;

if (delta != 0)
{
wantedDistance = Mathf.Max(distanceOperation.min, distance - delta * distanceOperation.pinchSpeed);
pinched = true;
}
}

//スワイプで旋回
//・SwipeInput を使用して旋回する。
//http://fantom1x.blog130.fc2.com/blog-entry-250.html
public void OnSwipe(Vector2 dir)
{
if (!turnOperation.swipeEnable)
return;

if (dir == Vector2.left)
TurnLeft();
else if (dir == Vector2.right)
TurnRight();
}


//左旋回
public void TurnLeft()
{
angle = Mathf.Repeat(MultipleCeil(angle - turnOperation.angle, turnOperation.angle), 360f);
}

//右旋回
public void TurnRight()
{
angle = Mathf.Repeat(MultipleFloor(angle + turnOperation.angle, turnOperation.angle), 360f);
}


//以下、static method

//より小さい倍数を求める(倍数で切り捨てられるような値)
//http://fantom1x.blog130.fc2.com/blog-entry-248.html
static float MultipleFloor(float value, float multiple)
{
return Mathf.Floor(value / multiple) * multiple;
}

//より大きい倍数を求める(倍数で繰り上がるような値)
static float MultipleCeil(float value, float multiple)
{
return Mathf.Ceil(value / multiple) * multiple;
}

//2D(XY平面)での方向ベクトル同士の角度を符号付きで返す(度)
//http://fantom1x.blog130.fc2.com/blog-entry-253.html#AngleWithSign
static float AngleXZWithSign(Vector3 from, Vector3 to)
{
Vector3 projFrom = from;
Vector3 projTo = to;
projFrom.y = projTo.y = 0; //y軸を無視する(XZ平面に投影する)
float angle = Vector3.Angle(projFrom, projTo);
float cross = CrossXZ(projFrom, projTo);
return (cross != 0) ? angle * -Mathf.Sign(cross) : angle; //2D外積の符号を反転する
}

//2Dでの外積を求める(XY平面)
//http://fantom1x.blog130.fc2.com/blog-entry-253.html#Cross2D
static float CrossXZ(Vector3 a, Vector3 b)
{
return a.x * b.z - a.z * b.x;
}
}
}

 うむ、「SmoothFollow2」より随分と簡潔に書いたつもりだが、めっちゃ長いな(笑)。セットアップや使い方などは「SmoothFollow2」と変わらないので、そちらに丸投げしよう(必要ならこちらを参照して欲しい)。また、SmoothFollow2 との主な相違点はこちらにまとめて置いた。ここでは新しく加わった要素と、モバイル対応のために追加した機能などを主に説明しておこう。

 1つ目は「autoInitOnPlay」のオプションだ。これは起動時に設定された対象(target)から、距離(distance)、高さ(height)、角度(preAngle)を自動算出するオプションで、true のときはインスペクタで設定している distance, height, preAngle は上書きされるので注意しよう。簡単に言えばこれはエディタで見えている感じをそのまま反映させるオプションである。ただし、カメラは常に LookAt(対象の方を向く)されているので、カメラ自体の回転は変わることに注意しよう。どうしてもエディタと同じような見え方にしたい場合は、あらかじめカメラの高さや方向を合わせておけば良い。そうすればオプションを true でエディタと全く同じ見え方になる。そしてその起動時に取得された距離(distance)、高さ(height)、角度(preAngle)などは「ResetOperations()」を呼び出すことで、いつでも元に戻せる

 次にドラッグなどを認識できる画面領域を設定する「validArea」を説明しておこう。これは「ピンチ」「スワイプ」「ロングタップ」に使われているものと全く同じもので、画面左下を基準に (0, 0) とし、画面右上を (1, 1) として、その中でしか操作を認識しないというものである。例えば、画面の下 1/4 ほどにはアイコンなどを置き、ドラッグなどの操作対象にしたくないときは validArea を (X, Y) = (0, 0.25), (W, H) = (1, 0.75) のように設定すれば、下の方でドラッグしても無視される(回転などはしない)。

 それ以外には「AngleOperation」と「TurnOperation」の違いを説明しておこう。「AngleOperation」の方は以前から備わっている対象を中心に自由に回転角を操作できるものだ。これに対して「TurnOperation」の方は同じように回転はするが、必ず「TurnOperation.angle」で設定された一定角度まで回転する。これはクォータービューのゲームのように常に一定角度(だいたい45度とか90度とか)で覗きたいときの回転方法だ。内部的には同じ値を参照しているので、どちらか一方を使いたい場合は「AngleOperation」と「TurnOperation」内の「~Enable」でオン・オフを設定すれば良い。

 あとはピンチ(PinchInput)スワイプ(Swipe)との連携方法だが、それらは以降を参照して欲しい。基本的にはインスペクタでセットアップするだけだ。



■スワイプ(SwipeInput)と連携して一定角度の旋回をする(TurnOperation 連携)

 ここでは以前に作った「SwipeInput」を利用して、スワイプで一定角度の旋回をする(TurnOperation 連携)するセットアップを紹介しておこう。アタッチする GameObject は任意で構わないが、インスペクタから SwipeInput.OnSwipe に SmoothFollow3.OnSwipe を登録(Dynamic の方)をすれば良いだけである。旋回の角度は「TurnOperation.angle」で設定できるので、あとは「Swipe Enable」がオンになっていることだけを確認すればOKだ(デフォルト=オン)。


 ただし、SmoothFollow3.OnSwipe() は左右のスワイプしか受け付けない。OnSwipe() 自体の引数は Vector2 なので、例えばボタンから Vector2.leftVector2.right を送れば同じように旋回できる。

 コールバックの登録の仕方がわからない場合は、以下の記事も参照して欲しい。

【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録



■ピンチ(PinchInput)と連携して遠近を操作する(DistanceOperation 連携)

 もう1つ、以前に作った「PinchInput」を利用して、ピンチで距離の遠近を操作する(DistanceOperation 連携)するセットアップを紹介しておこう。アタッチする GameObject は任意で構わないが、インスペクタから PinchInput.OnPinch に SmoothFollow3.OnPinch を登録(Dynamic の方)をすれば良いだけだ(OnPinchStart は必要ない)。遠近操作の速度は「DistanceOperation.pinchSpeed」で主に調整すれば良いだろう(pinchDamping はスムーズに移動する速度で、大きくすれば急速に移動できる)。あとは「PinchInput.isNormalized:オン」「SmoothFollow3 の Pinch Enable」がオンになっていることだけを確認すればOKだ(デフォルト=オン)。


 SmoothFollow3.OnPinch() で受け取る値は主に delta(正規化されたピンチの距離差分)でピクセル単位でないことに注意しよう(PinchInput を使用していて、isNormalized=true であれば気にする必要ない:デフォルト設定)。自分で実装する場合は、以前のピンチの記事を参照して欲しい。これは画面解像度に依存しないようにした処置なので、同じような方法を用いてるなら「DistanceOperation.pinchSpeed」を調整すれば他のスクリプトからも利用できるだろう。この値(delta)は線形的な相対量なので、例えばボタンを押し続けると一定間隔で移動量を与えるなら(符号に注意)、同じように遠近操作できる。

 コールバックの登録の仕方がわからない場合は、以下の記事も参照して欲しい。

【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録



■SmoothFollow2 との相違点・対応パラメタ一覧

 大ざっぱに「SmoothFollow2」との主な変更点は、以下のようになる。

・一定角度の回転(45度/90度ごと等の旋回:Turn Operation)機能を追加。
・SwipeInput のコールバックでのスワイプで一定角度の旋回の連携を追加。
・PinchInput のコールバックでのピンチで距離の操作の連携を追加(モバイル用)。
・起動時に設定された対象(target)から、距離(distance)、高さ(height)、角度(preAngle)を算出するオプションを追加。
・初期状態へのリセットメソッド(ResetOperations())を追加。
・ドラッグの認識する画面上の領域(validArea)を追加。
・各設定をクラスで分けたので、変数名が変更された(機能は全て同じ)。

 角度や距離、高さの仕様はそのままなので、その辺りは以前の記事の SmoothFollow2の図解 を見て欲しい。

●変更されたパラメタ一覧(インスペクタ)
SmoothFollow2SmoothFollow3
Height DampingHeight Operation>Damping
Rotation DampingAngle Operation>Damping
Distance DampingDistance Operation>Damping
Angle Key OperationAngle Operation>Key Enable
Angle Key SpeedAngle Operation>Key Speed
Angle Key LeftAngle Operation>Key Left
Angle Key RightAngle Operation>Key Right
Angle Drag OperationAngle Operation>Drag Enable
Angle Drag SpeedAngle Operation>Drag Speed
Height Key OperationHeight Operation>Key Enable
Height Key SpeedHeight Operation>Key Speed
Height Key UpHeight Operation>Key Up
Height Key DownHeight Operation>Key Down
Height Drag OperationHeight Operation>Drag Enable
Height Drag SpeedHeight Operation>Drag Speed
Distance Key OperationDistance Operation>Key Enable
Distance MinDistance Operation>Min
Distance Key SpeedDistance Operation>Key Speed
Distance Key NearDistance Operation>Key Near
Distance Key FarDistance Operation>Key Far
Distance Wheel OperationDistance Operation>Wheel Enable
Distance Wheel SpeedDistance Operation>Wheel Speed

※基本的には各機能ごとのクラスにまとめた感じ。



■ユニティちゃんがアキバ上空を飛ぶ!(Android 版)

 以前に書いた「ユニティちゃんを飛行させる!」をスマホ用にビルドしたデモです。操作パッドは StandardAssets の CrossPlatformInput を使ってます(画像だけ替えてる)。アキバモデル(秋葉原っぽい街)は多少スケールを大きくしてますね(キャラに合わせるならもっと大きい方が実寸に近いと思うが、全体を眺めたいだけなので適当)。最適化などは何もしてないので、眺める角度によってはフレーム落ちしますが、タッチ操作を試すだけなら十分でしょう(画面半分から上でドラッグ、スワイプ、ピンチなどが実装してある)。当たり判定も無いので建物は基本的に突き抜けます(笑)。

 スタート地点は「昭和通り」のバスターミナル辺りですね。現在はつくばエクスプレスの改札口もあります。たぶん全部(100個)集めるのに1時間くらいはかかります。コインの獲得数は保存されているので、無理せず暇な時にでもどうそ。どうしても全部のコインが見つからないときはマップを載せておくので参考にして下さい(見ても難しいと思う)。結構良く街の並びも再現されてますね。色んな場所を飛んでみて楽しんで下さい。

(※) Unity 2017.3.0f3 - 2018.1.5f1 / Windows10(x64) でビルド。Galaxy S7 Edge (Android 7.0) で確認。

デモアプリのダウンロード (Google Drive を利用)


Android 4.2以上
※「提供元不明アプリのインストール」許可が必要です。



※どうしても100個見つからないときは、以下にコインの位置を示したマップを掲載して置きます。参考にして下さい。

[>>クリックでコインのマップを見る(※ネタバレ注意)]



 参考までにインスペクタの設定はこちら(クリックでキャプチャ画像表示)。一応、横長(Landscape)前提で「Width Reference」(画面の横幅を長さの基準とする)でやってしまってますが、画面をローテーションできるアプリなら、短い方に合わせた方が良いかも知れません。



※この記事のスクリプトはプラグインのライブラリにも同梱されています。


※とりあえず試してみたい方は、最新版をビルドした apk デモをダウンロードできます。動作確認にもどうぞ。

プラグインデモをダウンロード
(Google Drive を利用)


Android 4.2以上
※「提供元不明アプリのインストール」許可が必要です。


(関連記事)
【Unity】【C#】SmoothFollow に回転アングルと高さと距離の遠近機能を付けてみる
【Unity】【C#】SmoothFollow を C# で書いてみる
【Unity】【C#】ピンチ操作を取得してコールバックする
【Unity】【C#】スワイプ(フリック)を判定、方向を取得してコールバックする
【Unity】【C#】長押し(ロングタップ)を取得してコールバックする
【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録
【Unity】Androidのトーストやダイアログ、通知、音声認識、ハード音量操作など基本的な機能を使えるプラグインを作ってみた
【Unity】【C#】ユニティちゃんを飛行させる!


スポンサーサイト

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  Unityプラグイン  C#  カメラスクリプト 
tb: 0   cm: --

【Unity】【C#】SmoothFollow に回転アングルと高さと距離の遠近機能を付けてみる  


 前回、Standard Assets の「SmoothFollow.js」の C# 版を作ったが、せっかくなのでカメラアングルを左右回転する機能と、高さと距離のプロパティ(フィールド)を変更して自由に遠近できる機能を付けてみた。


●SmoothFollow.js の C#版 に左右回転アングルと高さと距離の遠近機能を追加したもの
using UnityEngine;
using System.Collections;

[AddComponentMenu("Camera-Control/SmoothFollow2")]
public class SmoothFollow2 : MonoBehaviour {

/** 追従するオブジェクト */
public Transform target;

/** Z方向の距離 */
public float distance = 2.0f;

/** Y方向の高さ */
public float height = 0f;

/** カメラアングル初期値 */
public float preAngle = 0f;

/** 上下高さのスムーズ移動速度 */
public float heightDamping = 2.0f;

/** 左右回転のスムーズ移動速度 */
public float rotationDamping = 3.0f;

/** 距離のスムーズ移動速度 */
public float distanceDamping = 1.0f;


/** 回転のキー操作の ON/OFF */
public bool angleKeyOperation = true;

/** 左右回転速度 */
public float angleKeySpeed = 45f;

/** 左旋回キー */
public KeyCode angleKeyLeft = KeyCode.Z;

/** 右旋回キー */
public KeyCode angleKeyRight = KeyCode.X;

/** カメラアングル相対値 */
private float angle;

/** 回転のドラッグ操作の ON/OFF */
public bool angleDragOperation = true;

/** ドラッグ操作での回転速度 */
public float angleDragSpeed = 10f;

/** マウス移動始点 */
private Vector3 startPos;


/** 高さのキー操作の ON/OFF */
public bool heightKeyOperation = true;

/** キー操作での移動速度 */
public float heightKeySpeed = 1.5f;

/** 高さ上へキー */
public KeyCode heightKeyUp = KeyCode.C;

/** 高さ下へキー */
public KeyCode heightKeyDown = KeyCode.V;

/** 高さのドラッグ操作での ON/OFF */
public bool heightDragOperation = true;

/** ドラッグ操作での高さ移動速度 */
public float heightDragSpeed = 0.5f;


/** 距離のキー操作の ON/OFF */
public bool distanceKeyOperation = true;

/** Z方向の最小距離 */
public float distanceMin = 1.0f;

/** 距離の移動速度 */
public float distanceKeySpeed = 0.5f;

/** 近くへキー */
public KeyCode distanceKeyNear = KeyCode.B;

/** 遠くへキー */
public KeyCode distanceKeyFar = KeyCode.N;

/** 距離のホイール操作の ON/OFF */
public bool distanceWheelOperation = true;

/** ホイール1目盛りの速度 */
public float distanceWheelSpeed = 7f;

/** 変化先距離 */
private float wantedDistance;


// Use this for initialization
void Start () {
angle = preAngle;
wantedDistance = distance;
}

// Update is called once per frame
void Update () {
//回転のキー操作
if (angleKeyOperation) {
if (Input.GetKey(angleKeyLeft)) {
angle += angleKeySpeed * Time.deltaTime;
if (angle >= 360f) {
angle -= 360f;
}
}
if (Input.GetKey(angleKeyRight)) {
angle -= angleKeySpeed * Time.deltaTime;
if (angle < 0f) {
angle += 360f;
}
}
}

//高さのキー操作
if (heightKeyOperation) {
if (Input.GetKey(heightKeyUp)) {
height += heightKeySpeed * Time.deltaTime;
}
if (Input.GetKey(heightKeyDown)) {
height -= heightKeySpeed * Time.deltaTime;
}
}

//ドラッグ操作
if (angleDragOperation || heightDragOperation) {
Vector3 movePos = Vector3.zero;
if (Input.GetMouseButtonDown(0)) {
startPos = Input.mousePosition;
}
else if (Input.GetMouseButton(0)) {
movePos = Input.mousePosition - startPos;
startPos = Input.mousePosition;
}
else if (Input.GetMouseButtonUp(0)) {
startPos = Vector3.zero;
}
if (movePos != Vector3.zero) {
//回転のドラッグ操作
if (angleDragOperation) {
angle += movePos.x * angleDragSpeed * Time.deltaTime;
if (angle < 0f) {
angle += 360f;
} else if (angle >= 360f) {
angle -= 360f;
}
}
//高さのドラッグ操作
if (heightDragOperation) {
height -= movePos.y * heightDragSpeed * Time.deltaTime;
}
}
}

//距離のキー操作
if (distanceKeyOperation) {
if (Input.GetKey(distanceKeyNear)) {
wantedDistance = distance - distanceKeySpeed;
if (wantedDistance <= distanceMin) {
wantedDistance = distanceMin;
}
}
if (Input.GetKey(distanceKeyFar)) {
wantedDistance = distance + distanceKeySpeed;
}
}

//距離のホイール遠近
if (distanceWheelOperation) {
float mw = Input.GetAxis("Mouse ScrollWheel");
if (mw != 0) {
wantedDistance = distance - mw * distanceWheelSpeed; //0.1 x N倍
if (wantedDistance <= distanceMin) {
wantedDistance = distanceMin;
}
}
}
}

void LateUpdate () {
if (target == null) {
return;
}

//追従先位置
float wantedRotationAngle = target.eulerAngles.y + angle;
float wantedHeight = target.position.y + height;

//現在位置
float currentRotationAngle = transform.eulerAngles.y;
float currentHeight = transform.position.y;

//追従先へのスムーズ移動距離(方向)
currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, wantedRotationAngle,
rotationDamping * Time.deltaTime);
currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightDamping * Time.deltaTime);
distance = Mathf.Lerp(distance, wantedDistance, distanceDamping * Time.deltaTime);

//カメラの移動
var currentRotation = Quaternion.Euler(0, currentRotationAngle, 0);
Vector3 pos = target.position - currentRotation * Vector3.forward * distance;
pos.y = currentHeight;
transform.position = pos;

transform.LookAt(target);
}
}


 プロパティはやたらと増えてしまったが、基本的な仕様はそのまま引き継いでいるつもり。インスペクターで細かく調整できるようにしたが、~Operation のチェックをすべて外せば、実質、SmoothFollow と同じになる。製作中のチェック用に使うのも良いかもしれない。

 LateUpdate() の部分はあまり変えてないが、カメラのアングル位置を相対変化させる計算と、距離(distance)をスムーズに移動するように Mathf.Lerp() を追加している。Update() は主に操作用のコードとなっているが、単純な計算しかしてないので、改造するのは簡単だろう。Start() は手抜きして初期化が必須なものしか書いてないが、本来はプロパティ値が負の値だと動作が変になるので、値のチェックを追加しても良いかもしれない。distance だけは distanceMin で制限はしてある(distanceMin 自体は0より上)。


●プロパティ図解





 使い方としては、元の「SmoothFollow.js」と同じ。カメラにスクリプトを追加して、Target にキャラを設定するだけだ。ただ使ってみると、ユニティちゃんみたいなキャラは足元が基準座標となっているため、距離(distance)を近づけると、足元に寄ってしまう。キャラの中心あたりに視点を設定したければ、空の GameObject をキャラの子要素にして、そのオブジェクトをカメラスクリプトの Target に設定すれば良い


●空の GameObject(TargetPos)を追加して、子要素にする


●カメラの視点にしたい位置を Position に設定


●スクリプトの Target に視点用オブジェクトを設定



 具体的な動作を見てみたい場合は、下記のリンクからサンプルをどうぞ。



(関連記事)
【Unity】【C#】SmoothFollow を C# で書いてみる
【Unity】【C#】ユニティちゃんをサクっと簡単に動かす!
【Unity】【C#】ユニティちゃんを飛行させる!
【Unity】クエリちゃんを動かす!
【Unity】【C#】クエリちゃんを飛行させる!
【Unity】SDクエリちゃんを動かす!
【Unity】【C#】SDクエリちゃんを飛行させる!
【Unity】プロ生ちゃんを動かす!
【Unity】プロ生ちゃんを飛行させる!




このコンテンツの一部には『ユニティちゃんライセンス』で許諾されたアセットが使用されています。
Portions of this work are licensed under Unity-Chan License




category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  C#  カメラスクリプト  サンプル 
tb: 0   cm: --

【Unity】【C#】SmoothFollow を C# で書いてみる  


 色々サンプルみてると Standard Assets の「SmoothFollow.js」を使っていて、C# スクリプトから SmoothFollow をクラス名(型)でそのまま使っていたりするけど(SmoothFollow sf = ~みたいに)、なぜか今使っているバージョン(Unity4.6.1f1/MonoDevelop4.0.1)では JavaScript(UnityScript) で書かれたコードはクラス名(型)として使えないんだよね。Joystick.js なんかも入力候補に出てこない(手入力してもエラー。ルートに置いてもダメ)。インスト失敗したのかな…?まぁいいやと思って、試しに js をそのまま C# スクリプトに書き直してみたら、うまく行った。

●SmoothFollow.js の C# 版
using UnityEngine;
using System.Collections;

[AddComponentMenu("Camera-Control/SmoothFollow")]
public class SmoothFollow : MonoBehaviour {

/** 追従するオブジェクト */
public Transform target;

/** Z方向の距離 */
public float distance = 10.0f;

/** Y方向の高さ */
public float height = 5.0f;

/** 上下高さのスムーズ移動速度 */
public float heightDamping = 2.0f;

/** 左右回転のスムーズ移動速度 */
public float rotationDamping = 3.0f;

void LateUpdate() {
if (target == null) {
return;
}

//追従先位置
float wantedRotationAngle = target.eulerAngles.y;
float wantedHeight = target.position.y + height;

//現在位置
float currentRotationAngle = transform.eulerAngles.y;
float currentHeight = transform.position.y;

//追従先へのスムーズ移動距離(方向)
currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, wantedRotationAngle,
rotationDamping * Time.deltaTime);
currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightDamping * Time.deltaTime);

//カメラの移動
var currentRotation = Quaternion.Euler(0, currentRotationAngle, 0);
Vector3 pos = target.position - currentRotation * Vector3.forward * distance;
pos.y = currentHeight;
transform.position = pos;

transform.LookAt(target);
}
}


 書いた後で気がついたんだけど、英語のフォーラムの方に js→C# の自動変換をしてバグったという質問に、修正版が書かれてあったけど、ほぼ同じだった。まぁ、日本語でコメント書いてあるから日本人向けということで(笑)。

 自動変換でバグる理由は js で transform.position.y = currentHeight; となっている部分をそのまま C# で書くと、テンポラリな値になってしまって、無効になってしまうからなんだよね。Java なんかもこういう書き方するから、私も最初間違えた。「あれ?なんか警告出てるな」って思って修正したので、すぐ上手く動いたけどね。自動変換ツールっていうのも微妙なんだな…。私は勉強にもなるし、手と頭で変換するからツールは使ったことないけど。


(関連記事)
【Unity】【C#】SmoothFollow に回転アングルと距離の遠近機能を付けてみる


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  C#  カメラスクリプト 
tb: 0   cm: --

【Unity】【C#】ユニティちゃんをサクっと簡単に動かす!(Unity4)  


 わりと自由に使えるユニティちゃん。ググると、Locomotion Starter Kit を利用したり、Animator を自分で作る方法があったが、多少面倒くさいな~と思ったので、Asset Store からインポートしたものだけでサクっと簡単に動かすテスト。

(※) Unity 4.6.1f2 / Windows8.1(x64) で確認




■インポート~ユニティちゃんを動かす

1.まず新しいプロジェクトを作ったら、「Window」メニューの「Asset Store」からユニティちゃんの3Dモデルをインポート。
Asset Store 内では、横にあるメニューから、「3Dモデル > キャラクター」で、「"Unity-chan!" model」を探す。たぶんすぐ見つかる。オフィシャルサイトからダウンロードしたパッケージを使っても良い(オフィシャルの方が最新版)。





2.とりあえず動き回れる床を作りたいので、「GameObject」メニューから「Plane」を作る。位置は原点に、大きさは適当に 100 くらいにしておく。





3.床が単色では何となくさびしいので、プロジェクトエクスプローラから「UnityChan>Stage>Textures」フォルダを開き、好きなテクスチャを Plane にドラッグ&ドロップする。 Tiling は 500 くらいにしておく。







4.このままだと真っ暗なので、「GameObject」メニューから「Directinal Light」を適当に置く。Intensity だけを 1 にしておく。ちなみにユニティちゃんのシェーダーは Directinal Light 1つが良いらしい。





5.次に、ユニティちゃんの 3Dモデルを配置する。一番簡単なのは、プロジェクトエクスプローラから「UnityChan>Prefabs>for Locomotion」フォルダの中にある「unitychan」を Hierarchy にドラッグ&ドロップすることだ。このプレファブにははじめから「Animator」「Capsule Collider」「Rigidbody」とユニティちゃんをキー操作で動かすスクリプト「UnityChanControlScriptWithRgidBody」が組み込まれているので、実はこれだけでプレイボタンで動かせる。特にパラメタを変更する必要はない。







6.ただしこのままだとちょっとカメラが遠いのと、カメラは固定でユニティちゃんが画面からはみ出たりすると見えなくなってしまうので、カメラをキャラに追従させたい。超簡単な方法は「Main Camera」を「unitychan」の子要素にしてしまう方法だ。Hierarchy で「Main Camera」を「unitychan」にドラッグ&ドロップして、カメラの位置を調整するだけで良い。こうするとユニティちゃんと一緒にカメラも動く。ここではアングルを正面からにしてみた。一応、これで完成。シーンは適当な名前で保存しておく。







(※) もう1つの方法はプロジェクトエクスプローラで「UnityChan>Scripts」フォルダに「CameraController」があるので、「Main Camera」に組み込んでも良い(「Component」メニューの「Script」で「CameraController」を追加)。Inspector で「Focus Obj」に Hierarchy の unitychan をドラッグ&ドロップすれば追従できる。また「Focus」のパラメタでカメラの中心を調整できる。これを使えばホイールでズームイン・アウトもできる。

>>「SmoothFollow に回転アングルと距離の遠近機能を付けたもの」を使っても良い。


7.空が寂しければ「Skybox」を追加しても良い。「Assets」メニューから「Import Package>Skyboxes」で素材をインポート。「Main Camera」に「Component」メニューから「Rendering>Skybox」を追加。Inspector で好きなものを選ぶ。またプロジェクトエクスプローラから「Standard Assets>Skyboxes」フォルダからドラッグ&ドロップでも変更できる。Game ビュー(タブ) にしておけば確認しやすい(※ Unity4 でのやり方。Unity5 では「Lighting」の設定項目に Skybox がある)。







今回はインポートから地面~空まで作ってしまったが、既に何らかのシーンがある場合、要点を抜き出せば、「UnityChan>Prefabs>for Locomotion」フォルダの中にある「unitychan」をシーンにつっこんで、「Main Camera」を「unitychan」の子要素にすれば良いということだ。





■キャラの周りを旋回するカメラを作る

ここからは少しオマケ的になるが、もうちょっとカメラのアングルを自由に動かしたいので、カメラ操作のスクリプトを自作しよう。

仕様としては先ほどの「Main Camera」を「unitychan」の子要素の状態で調整した上で、キャラの周りをぐるぐる回るようなカメラを作ってみる。ついでにズームイン・アウトもスムーズにしてみる。

1.もし「Main Camera」に「CameraController」を組み込んであるのだったら、スクリプトを「Remove Component」で解除する。次いで新しく「CameraController2」とでもして C# スクリプトを作成しよう(Inspector の Add Component から「New Script」で作成)。



2.作成したスクリプトファイルを MonoDevelop などエディタで開いたら、以下のスクリプトをコピペする。

public class CameraController2 : MonoBehaviour {

public GameObject player; //キャラのオブジェクト
public Vector3 focus = Vector3.zero; //中心座標調整用
public float angle = 45f; //回転量(度)
public float drag = 0.3f; //マウスドラッグでの移動量倍率(移動px x N倍)
public float wheel = 2f; //ホイール量の倍率(0.1 x N倍)

private float scrollAmount = 0f; //ホイール量のキュー
private const float SCROLL_DEC = 0.025f; //フレームごとのホイール減算量
private Vector3 startPos; //マウス移動始点

// Use this for initialization
void Start () {
if (player == null) {
player = GameObject.Find("unitychan");
}
}

// Update is called once per frame
void Update () {
//キャラを中心に左旋回
if (Input.GetKey(KeyCode.Z)) {
Camera.main.transform.RotateAround(player.transform.position + focus,
player.transform.up, angle * Time.deltaTime);
}
//キャラを中心に右旋回
if (Input.GetKey(KeyCode.X)) {
Camera.main.transform.RotateAround(player.transform.position + focus,
player.transform.up, -angle * Time.deltaTime);
}
//キャラを中心に上旋回
if (Input.GetKey(KeyCode.C)) {
Camera.main.transform.RotateAround(player.transform.position + focus,
player.transform.right, -angle * Time.deltaTime);
}
//キャラを中心に下旋回
if (Input.GetKey(KeyCode.V)) {
Camera.main.transform.RotateAround(player.transform.position + focus,
player.transform.right, angle * Time.deltaTime);
}

//マウスドラッグで回転
Vector3 movePos = Vector3.zero;
if (Input.GetMouseButtonDown(0)) {
startPos = Input.mousePosition;
}
else if (Input.GetMouseButton(0)) {
movePos = Input.mousePosition - startPos;
startPos = Input.mousePosition;
}
else if (Input.GetMouseButtonUp(0)) {
startPos = Vector3.zero;
}
if (movePos != Vector3.zero) {
Camera.main.transform.RotateAround(player.transform.position + focus,
player.transform.up, movePos.x * drag);
Camera.main.transform.RotateAround(player.transform.position + focus,
player.transform.right, movePos.y * drag);
}

//キャラを中心に近くへ
if (Input.GetKey(KeyCode.LeftShift)) {
Vector3 dir = player.transform.position + focus - Camera.main.transform.position;
Camera.main.transform.position += dir.normalized * Time.deltaTime;
}
//キャラを中心に遠くへ
if (Input.GetKey(KeyCode.LeftAlt)) {
Vector3 dir = player.transform.position + focus - Camera.main.transform.position;
Camera.main.transform.position -= dir.normalized * Time.deltaTime;
}

//ホイールで遠近
scrollAmount += Input.GetAxis("Mouse ScrollWheel") * wheel;
if (scrollAmount > 0) {
Vector3 dir = player.transform.position + focus - Camera.main.transform.position;
Camera.main.transform.position += dir.normalized * Time.deltaTime;
scrollAmount -= SCROLL_DEC;
scrollAmount = (scrollAmount < 0) ? 0 : scrollAmount;
}
if (scrollAmount < 0) {
Vector3 dir = player.transform.position + focus - Camera.main.transform.position;
Camera.main.transform.position -= dir.normalized * Time.deltaTime;
scrollAmount += SCROLL_DEC;
scrollAmount = (scrollAmount > 0) ? 0 : scrollAmount;
}
}

void setCameraPositionJumpView() {
//UnityChanControlScriptWithRgidBody のコールバック(Jump中カメラ)
}
}


3.スクリプトを保存したら、Unity のエディタに戻って、「Main Camera」の Inspector の「Player」に Hierarchy から「unitychan」をドラッグ&ドロップする(設定しない場合は自動で「unitychan」を探す)。また回転の中心をキャラの中心あたりにしたいので、Focus パラメタを調整する。これはユニティちゃんなどキャラの基準座標は、地面と接地する足元になっているためだ。



4.これでカメラ操作は完成。操作方法はコメントにも書いておいたが、キャラに向かって、[Z]で左旋回、[X]で右旋回、[C]で上旋回、[V]で下旋回、[Shift](左)でズームイン、[Alt](左)でズームアウトにしておいた。ついでにホイールのズームイン・アウト、マウスドラッグで旋回も対応しておいた。

(※) angle、wheel、SCROLL_DEC の値などは適宜調整しても良い。インスペクターでも調整できる。
(※) setCameraPositionJumpView() はユニティちゃんを動かすスクリプトからのコールバックだが使ってない。ジャンプ中カメラを換えたりするときに使うみたいだが、ここでは単なるエラー出力防止用だ(ジャンプするとコンソールに「SendMessage setCameraPositionJumpView has no receiver!」と出るので。放っておいても別に構わない)。

>>「SmoothFollow に回転アングルと距離の遠近機能を付けたもの」を使っても良い。





■Web Player 用にビルドする。ついでに WebTemplate を適用する方法 (Unity4)


Web テンプレート適用後


最後にこれもオマケだが、Web Player 用に Build するとき、Web テンプレートを使う方法。

1.インストールした Unity のフォルダ「Unity>Editor>Data>Resources>WebPlayerTemplates」をエクスプローラー等で開く。

2.ここに一般配布などされているWeb テンプレートをフォルダごとコピーする。またはプロジェクトエクスプローラにフォルダをつっこんでも良い。ただしその場合は、そのプロジェクト専用になる。

3.「File」メニューから「Build&Settings...」を開き、「Web Player」を選び、「Add Current」を押し、現在のシーンを登録する。



4.下にある「Player Settings...」を押すと Inspector に「Resolution and Presentation」という項目があるので、それをクリックして展開すると、先ほどコピーしたテンプレートが出てくるので選択する。



5.「Build&Settings...」に戻って「Build」ボタンを押すと出力するフォルダを聞いてくるので、適当なフォルダを作成して、出力する。

6.「~.html」と「~.unity3d」ファイルが出来るので、サーバーにアップすれば表示できる。ローカルでもブラウザに「~.html」をつっこめば確認できる。




 最後まで読んだ方、お疲れ様でした(笑)。アニメーションも「UnityChan>Amimators」にいくつか入っているので、「Animator」を「UnityChanActionCheck」や「UnityChanARPose」に換えれば、色んなポーズを見ることができます。蹴りのモーションもなかなかいいね。このサンプルでも止まっているとき、Space キーを押すと背伸びするけど、とても可愛い。


(参考になる書籍)



(関連記事)
【Unity】【C#】ユニティちゃんを飛行させる!
【Unity】SDユニティちゃんを動かす!
【Unity】クエリちゃんを動かす!
【Unity】SDクエリちゃんを動かす!
【Unity】プロ生ちゃんを動かす!
【Unity】SDプロ生ちゃんを動かす!
【Unity】【C#】SmoothFollow に回転アングルと距離の遠近機能を付けてみる




このコンテンツの一部には『ユニティちゃんライセンス』で許諾されたアセットが使用されています。
Portions of this work are licensed under Unity-Chan License


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: C#  ユニティちゃん  サンプル  カメラスクリプト 
tb: 0   cm: --


プロフィール

Social

検索フォーム

全記事一覧

カテゴリ

ユーザータグ

最新記事

リンク

PR

▲ Pagetop