bate's blog

調べたこと実装したことなどを取りとめもなく書きます。

オブジェクトを掴む2

using UnityEngine;
using System.Collections;


/**
 * @breif	カメラ制御.
 */
public class CameraController {
	
	/* カメラ種類. */
	enum CameraType {
		Free,
		Focus,
		Follow,
	}
	
	/* カメラ種類. */
	CameraType m_CameraType = CameraType.Free;
	/* 追従ターゲット. */
	GameObject m_Target = null;
	/* フォーカス位置. */
	Vector3 m_FocusPosition = Vector3.zero;
	/* フォーカス時間. */
	float m_FocusTime = 0.0f;
	/* 現在時間. */
	float m_CurrentTime = 0.0f;
	/* スタート位置. */
	Vector3 m_StartCameraPosition = Vector3.zero;
	
	/**
	 * @brief	コンストラクタ.
	 */
	public CameraController() {
	}
	
	/**
	 * @brief	更新.
	 */
	public void Update() {
		switch(m_CameraType) {
		case CameraType.Free:
			break;
		case CameraType.Focus:
			UpdateFocus();
			break;
		case CameraType.Follow:
			break;
		}
	}
	
	/**
	 * @brief	フォーカス設定.
	 */
	public void SetFocus(Vector3 focusPos, float time) {
		m_CameraType = CameraType.Focus;
		m_FocusTime = time;
		m_CurrentTime = 0.0f;
		m_StartCameraPosition = Camera.main.transform.position;
		
		Ray ray = new Ray(focusPos, -1.0f*Camera.main.transform.forward);
		Plane plane = new Plane(Vector3.up, new Vector3(0, m_StartCameraPosition.y, 0));
		float dist = 0.0f;
		if(plane.Raycast(ray, out dist)) {
			m_FocusPosition = ray.GetPoint(dist);
		}
		else {
			Debug.Break();
			m_CameraType = CameraType.Free;
		}
	}
	
	/**
	 * @brief	追従設定.
	 */
	public void SetFollow(GameObject target) {
		m_CameraType = CameraType.Follow;
		m_Target = target;
	}
	
	/**
	 * @brief
	 */
	void UpdateFocus() {
		if(m_CurrentTime > 1.0f) {
			m_CameraType = CameraType.Free;
			return;
		}
		m_CurrentTime += Time.deltaTime/m_FocusTime;
		Vector3 curPos = (1.0f-m_CurrentTime) * m_StartCameraPosition + m_CurrentTime * m_FocusPosition;
		Camera.main.transform.position = curPos;
	}
}
/**
 * @brief	オブジェクトをフリック.
 * @note	掴んで離す処理をコルーチンで.
 */
public class FlickMain : MonoBehaviour {
	
	/* 3Dルート. */
	GameObject m_3DRoot = null;
	/* ワールドオフセット. */
	Vector3 m_WorldOffset = Vector3.zero;
	/* カメラコントローラー. */
	CameraController m_CameraController = null;
	/* 板上の最後の位置. */
	Vector3 m_LastOnPlane = Vector3.zero;
	
	void Awake() {
		m_CameraController = new CameraController();
	}

	/**
	 * @brief	更新の最初に一度だけ呼ばれる.
	 * @note	コルーチンスタート.
	 */
	void Start () {
		m_3DRoot = GameObject.Find("3DRoot");
		StartCoroutine(Flick());
	}
	
	/**
	 * @brief	更新.
	 * @note	コルーチン任せなので空.
	 */
	void Update () {
		m_CameraController.Update();
	}
	
	/**
	 * @brief	GUI.
	 */
	void OnGUI() {
		if(GUILayout.Button("Title")) {
			Application.LoadLevel("Title");
		}
	}
	
	/**
	 * @brief	フリックを行うコルーチン.
	 */
	IEnumerator Flick() {
		
		/* フリック対象. */
		GameObject target = null;
		/* ループ終了フラグ. */
		bool isEnd = false;
		/* フリック可能レイヤー. */
		int layer = 1<<LayerMask.NameToLayer("Flick");
		
		/* 無限ループ. */
		while(true) {
			
			yield return 0;
			
			/* フリック対象に触れるまでループ. */
			while(!isEnd) {
				if(Input.GetMouseButtonDown(0)) {
					Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
					RaycastHit hit;
					if(Physics.Raycast(ray, out hit, Mathf.Infinity, layer)) {
						target = hit.collider.gameObject;
						if(target != null) {
							/* フリック対象に触れた. */
							isEnd = true;
							target.renderer.material.color = Color.red;
						}
					}
				}
				/* コルーチンを抜ける場所かつ再開場所. */
				yield return 0;
			}
			
			bool isOnPlane = false;
			while(Input.GetMouseButton(0)) {
				/* フリック対象を離されていない. */
				yield return 0;
				
				/* スクリーン上のクリック位置から直線を発射. */
				Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
				/* 移動可能平面. */
				Plane plane = new Plane(Vector3.up, 0.0f);
				Vector3 p = target.transform.position;
				float dist = 0.0f;
				if(plane.Raycast(ray, out dist)) {
					p = ray.GetPoint(dist);
					target.transform.position = p;
				}
				isOnPlane = UpdateLastOnPlane(ref m_LastOnPlane, p);
				AdjustSceneOnFlick();
			}
			
			target.renderer.material.color = Color.white;
			if(!isOnPlane) {
				target.transform.position = m_LastOnPlane;
			}
			
			/* 戻す. */
			float time = 0.0f;
			Vector3 spos = m_3DRoot.transform.position;
			Vector3 epos = Vector3.zero;
			m_CameraController.SetFocus(target.transform.position, 1.0f);
			while(time <= 1.0f) {
				m_3DRoot.transform.position = 
					(1.0f-time)*spos + time*epos;
				time += Time.deltaTime;
				yield return 0;
			}
			m_3DRoot.transform.position = Vector3.zero;
			
			isEnd = false;
		}
	}
	
	/**
	 * @brief	Planeレイヤーのオブジェクトから離れる直前を捉える.
	 * @note	posから-Y方向に直線を引いた時にPlaneと交点があるかどうか.
	 */
	bool UpdateLastOnPlane(ref Vector3 save, Vector3 pos) {
		Ray ray = new Ray(pos+10.0f*Vector3.up, -Vector3.up);
		RaycastHit hit;
		int layer = 1<<LayerMask.NameToLayer("Plane");
		if(Physics.Raycast(ray, out hit, Mathf.Infinity, layer)) {
			GameObject obj = hit.collider.gameObject;
			if(obj != null) {
				save = hit.point;
				return true;
			}
		}
		return false;
	}
	
	/**
	 * @brief	画面端ならワールドを動かす.
	 */
	void AdjustSceneOnFlick() {
		Vector3 offset = Vector3.zero;
		Vector3 mouse_pos = Input.mousePosition;
		Vector3 screen_pos = Camera.main.ScreenToViewportPoint(mouse_pos);
		if(screen_pos.x < 0.1f) {
			offset.x += 0.2f;
		}
		else if(screen_pos.x > 0.9f) {
			offset.x -= 0.2f;
		}
		if(screen_pos.y < 0.1f) {
			offset.z += 0.2f;
		}
		else if(screen_pos.y > 0.9f) {
			offset.z -= 0.2f;
		}
		if(m_3DRoot) {
			m_WorldOffset += offset;
			m_LastOnPlane += offset;
			m_3DRoot.transform.position += offset;
		}
	}
}