表示: 1 - 4 of 4 結果

Unityで作ったゲームをiPhoneで再生すると、音が小さくなる問題

がんばってXcodeで書き出したゲームやアプリをアイフォンでテストしてみたら、なんか異様に音が小さかったことありませんか。私はありました。なぜかAndroidでは問題がないのです。それが中々手強かった...以下直し方です。

Prepare iOS for Recordingのチェックボックスをオフにする

これだけです。場所はUnity > Project Settings > Player > iOS > Other Settings > Configuration > Prepare iOS for Recording 中央あたりにあります。


マイク録音 API を初期化するには、このオプションを有効にします。これにより、録音の遅延は少なくなります (iPhone ではオーディオ出力の再ルーティングはイヤホン経由でのみですが)。

要するに、iPhoneを電話として使う時に音小さいですよね。あれになってるとのことです。私は音声のラウドネスを上げて書き出し直したり、とにかく試しましたが直らなくて大変でした。以下私が試してみてダメだったこと一覧です。この方法で直る人もいるかも。

1. 音量設定の確認:

UnityのAudio Mixer: Audio Mixerを使用している場合、マスターボリュームや各グループのボリュームが適切に設定されているか確認してください。特に、iOSプラットフォーム向けの設定が正しいかどうかを確認します。

2. AudioSourceの設定:

AudioSourceコンポーネント: 各AudioSourceの音量設定を確認し、必要に応じて調整します。特に、音量が0.5未満になっている場合は、デフォルト値を1に設定してください。

3. iOS固有の音量調整 :

音量倍率の見直し: IOS_VOLUME_MULTIPLIER を2倍にしていますが、これが適切かどうか再評価してください。通常、iOSデバイスの音量は1.0〜1.5倍程度で十分なことが多いです。

private const float IOS_VOLUME_MULTIPLIER = 2f; // iOSデバイスでの音量倍率

private void ApplyVolume(float value)
{

#if UNITY_IOS
	value *= IOS_VOLUME_MULTIPLIER; // iOSデバイスでは音量を2倍に
#endif

	BGMManager.Instance.ChangeBaseVolume(value);
	SEManager.Instance.ChangeBaseVolume(value);
}

4. オーディオファイルの形式:

オーディオファイルの最適化: 使用しているオーディオファイルが適切な形式(例:.wav, .mp3)であるか確認し、必要に応じて再エンコードします。また、ビットレートやサンプリングレートも影響することがあります。

5. デバイス設定の確認:

iPhoneの音量設定: デバイス自体の音量設定(サウンド設定)やミュートスイッチがオフになっているか確認してください。また、アプリ内で音量が適切に反映されているかもチェックします。

6. テストとフィードバック:

異なるデバイスでテスト: 他のiOSデバイスでも同様の問題が発生するかどうか確認し、特定のモデルに問題があるかどうかを特定します。

Unity Community でも色々みつかりました。

皆様のお役に立てます様に。

perplexityのサイトの右カラムを消す

個人的に一番使いやすいAIのperplexity。とにかく問題解決能力に優れてる。キレイなコードではないかもしれないけど、それは解決してからキレイに書き直せばいい。他のAIの感想載せとく。

  • Gemini:いらん提案をしてくるくせに、コードを書いてくれない
  • ChatGPT:良くも悪くも普通。コードを全文載せてくれるけど、その自ら提案したコードをコピペしてもう一度聞くと、ここがダメ、あれがダメ、と永遠に終わらない
  • Claude:困ったときの最終手段。賢いけど、無料じゃ何回も使えない

ということで本題。perplexityサイトでは右によくわからない、他のWebから取ってきた情報が表示されるけど、今回はこれを消す。

  1. Stylusをインストール
    Chromeの機能拡張でcssをいじれるものがある。
  1. perplexityページを開いて以下のコードを入力。
/* 右カラムを消す */
.col-span-4.isolate {
    display: none !important;
    position: absolute !important;
    width: 0 !important;
    height: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
    overflow: hidden !important;
    clip-path: none;
    white-space: nowrap !important;
    border: 0 !important;
}

/* その分広げる */
.col-span-8 {
    grid-column: span 12 / span 12;
}

/* 入力欄を伸ばす */
.grow {
    min-width: 800px !important;
}
  1. 完成

ご使用は自己責任で。

カメラ移動スクリプト

1.直接的な位置の更新:

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;

[RequireComponent(typeof(Camera))]
public class SceneViewCamera_1 : MonoBehaviour
{
	[SerializeField] private float pinchSpeed;
	[SerializeField] private float dragThreshold;

	private Vector2 touchStartPos;
	private float initialFingersDistance;
	private Vector3 initialCameraPosition;
	private bool isPinching = false;
	private int? activeTouchId = null;
	private bool canMoveCamera = false;

	private Camera mainCamera;
	private Transform cameraTransform;

	private RaycastHit[] raycastHitCache = new RaycastHit[1];
	private Dictionary<int, Vector2> touchPositions = new Dictionary<int, Vector2>();

	private void Start()
	{
		mainCamera = Camera.main;
		cameraTransform = transform;
	}

	private void Update()
	{
		TouchUpdate();
	}

	private void TouchUpdate()
	{
		if (Input.touchCount == 1)
		{
			HandleSingleTouch(Input.GetTouch(0));
		}
		else if (Input.touchCount == 2)
		{
			HandlePinch(Input.GetTouch(0), Input.GetTouch(1));
		}
		else
		{
			ResetTouchState();
		}
	}

	private void HandleSingleTouch(Touch touch)
	{
		switch (touch.phase)
		{
			case TouchPhase.Began:
				if (!activeTouchId.HasValue)
				{
					activeTouchId = touch.fingerId;
					touchStartPos = touch.position;
					touchPositions[touch.fingerId] = touch.position;
					canMoveCamera = CheckIfCanMoveCamera(touch.position);
					Debug.Log($"Touch began: canMoveCamera = {canMoveCamera}");
				}
				break;

			case TouchPhase.Moved:
				if (activeTouchId == touch.fingerId && canMoveCamera)
				{
					float dragDistance = Vector2.Distance(touchStartPos, touch.position);
					Debug.Log($"Drag distance: {dragDistance}");
					if (dragDistance > dragThreshold)
					{
						TouchDrag(touch.position);
						Debug.Log("Camera moved");
					}
				}
				break;

			case TouchPhase.Ended:
			case TouchPhase.Canceled:
				if (activeTouchId == touch.fingerId)
				{
					ResetTouchState();
				}
				break;
		}
	}

	private void HandlePinch(Touch touch1, Touch touch2)
	{
		if (touch1.phase == TouchPhase.Began || touch2.phase == TouchPhase.Began)
		{
			initialFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			initialCameraPosition = cameraTransform.position;
			isPinching = true;
		}
		else if ((touch1.phase == TouchPhase.Moved || touch2.phase == TouchPhase.Moved) && isPinching)
		{
			float currentFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			float scaleFactor = initialFingersDistance / currentFingersDistance;
			Vector3 newPosition = initialCameraPosition + (cameraTransform.forward * (1f - scaleFactor) * pinchSpeed);
			cameraTransform.position = newPosition;
		}
		else if ((touch1.phase == TouchPhase.Ended || touch2.phase == TouchPhase.Ended) && isPinching)
		{
			isPinching = false;
		}
	}

	private bool CheckIfCanMoveCamera(Vector2 touchPos)
	{
		if (EventSystem.current.IsPointerOverGameObject(activeTouchId.Value)) return false;

		if (TouchUtility.TryGetTouchedObject(out GameObject touchedObject, layerMask: LayerMask.GetMask("Ground", "EggCollider")))
		{
			return touchedObject.CompareTag("Ground");
		}
		return false;
	}

	private void TouchDrag(Vector2 touchPos)
	{
		if (!canMoveCamera) return;

		Vector2 diff = touchPos - touchStartPos;
		Vector3 worldSpaceDiff = mainCamera.ScreenToViewportPoint(new Vector3(diff.x, diff.y, 0)) - mainCamera.ScreenToViewportPoint(Vector3.zero);
		worldSpaceDiff.z = 0; // z軸の移動を無視

		// カメラの右方向と上方向のベクトルを取得
		Vector3 cameraRight = cameraTransform.right;
		Vector3 cameraUp = cameraTransform.up;

		// 移動量を計算(x軸とy軸を反転)
		Vector3 movement = (-worldSpaceDiff.x * cameraRight + -worldSpaceDiff.y * cameraUp) * 10f;

		Debug.Log($"Screen diff: {diff}, World space diff: {worldSpaceDiff}, Movement: {movement}");

		// カメラの位置を更新
		Vector3 newPosition = cameraTransform.position + movement;
		cameraTransform.position = newPosition;

		touchStartPos = touchPos;
	}

	private void ResetTouchState()
	{
		activeTouchId = null;
		isPinching = false;
		touchPositions.Clear();
		canMoveCamera = false;
	}
}

利点:
指の動きに正確に追従するため、ユーザーの意図通りの動きを実現できます。
フレームレートに依存せず、一貫した動きを提供します。
実装が比較的シンプルで理解しやすいです。
欠点:
急激な動きをすると、カメラの動きが不自然に感じる可能性があります。
高速な動きに対して、カメラが追いつけない場合があります。
スクリーン座標からワールド座標への変換が必要で、計算コストがやや高くなります。

2デルタ移動の使用:

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;

[RequireComponent(typeof(Camera))]
public class SceneViewCamera_2 : MonoBehaviour
{
	[SerializeField] private float pinchSpeed;
	[SerializeField] private float dragThreshold;
	[SerializeField] private float dragSpeed = 3f; // 新しく追加:ドラッグ速度調整用

	private Vector2 touchStartPos;
	private float initialFingersDistance;
	private Vector3 initialCameraPosition;
	private bool isPinching = false;
	private int? activeTouchId = null;
	private bool canMoveCamera = false;

	private Camera mainCamera;
	private Transform cameraTransform;

	private RaycastHit[] raycastHitCache = new RaycastHit[1];
	private Dictionary<int, Vector2> touchPositions = new Dictionary<int, Vector2>();

	private void Start()
	{
		mainCamera = Camera.main;
		cameraTransform = transform;
	}

	private void Update()
	{
		TouchUpdate();
	}

	private void TouchUpdate()
	{
		if (Input.touchCount == 1)
		{
			HandleSingleTouch(Input.GetTouch(0));
		}
		else if (Input.touchCount == 2)
		{
			HandlePinch(Input.GetTouch(0), Input.GetTouch(1));
		}
		else
		{
			ResetTouchState();
		}
	}

	private void HandleSingleTouch(Touch touch)
	{
		switch (touch.phase)
		{
			case TouchPhase.Began:
				if (!activeTouchId.HasValue)
				{
					activeTouchId = touch.fingerId;
					touchStartPos = touch.position;
					touchPositions[touch.fingerId] = touch.position;
					canMoveCamera = CheckIfCanMoveCamera(touch.position);
					Debug.Log($"Touch began: canMoveCamera = {canMoveCamera}");
				}
				break;

			case TouchPhase.Moved:
				if (activeTouchId == touch.fingerId && canMoveCamera)
				{
					TouchDrag(touch);
					Debug.Log("Camera moved");
				}
				break;

			case TouchPhase.Ended:
			case TouchPhase.Canceled:
				if (activeTouchId == touch.fingerId)
				{
					ResetTouchState();
				}
				break;
		}
	}

	private void HandlePinch(Touch touch1, Touch touch2)
	{
		if (touch1.phase == TouchPhase.Began || touch2.phase == TouchPhase.Began)
		{
			initialFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			initialCameraPosition = cameraTransform.position;
			isPinching = true;
		}
		else if ((touch1.phase == TouchPhase.Moved || touch2.phase == TouchPhase.Moved) && isPinching)
		{
			float currentFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			float scaleFactor = initialFingersDistance / currentFingersDistance;
			Vector3 newPosition = initialCameraPosition + (cameraTransform.forward * (1f - scaleFactor) * pinchSpeed);
			cameraTransform.position = newPosition;
		}
		else if ((touch1.phase == TouchPhase.Ended || touch2.phase == TouchPhase.Ended) && isPinching)
		{
			isPinching = false;
		}
	}

	private bool CheckIfCanMoveCamera(Vector2 touchPos)
	{
		if (EventSystem.current.IsPointerOverGameObject(activeTouchId.Value)) return false;

		if (TouchUtility.TryGetTouchedObject(out GameObject touchedObject, layerMask: LayerMask.GetMask("Ground", "EggCollider")))
		{
			return touchedObject.CompareTag("Ground");
		}
		return false;
	}

	private void TouchDrag(Touch touch)
	{
		if (!canMoveCamera) return;

		Vector2 deltaPosition = touch.deltaPosition;
		Vector3 worldSpaceDelta = mainCamera.ScreenToViewportPoint(new Vector3(deltaPosition.x, deltaPosition.y, 0)) - mainCamera.ScreenToViewportPoint(Vector3.zero);
		worldSpaceDelta.z = 0; // z軸の移動を無視

		// カメラの右方向と上方向のベクトルを取得
		Vector3 cameraRight = cameraTransform.right;
		Vector3 cameraUp = cameraTransform.up;

		// 移動量を計算(x軸とy軸を反転)
		Vector3 movement = (-worldSpaceDelta.x * cameraRight + -worldSpaceDelta.y * cameraUp) * dragSpeed;

		Debug.Log($"Delta position: {deltaPosition}, World space delta: {worldSpaceDelta}, Movement: {movement}");

		// カメラの位置を更新
		cameraTransform.position += movement;
	}

	private void ResetTouchState()
	{
		activeTouchId = null;
		isPinching = false;
		touchPositions.Clear();
		canMoveCamera = false;
	}
}

Touch.deltaPositionを使用して、フレーム間の指の動きを直接反映させます。
利点:
フレーム間の細かい動きを捉えられるため、滑らかな動きを実現できます。
Touch.deltaPositionを直接使用するため、計算が比較的シンプルです。
連続的な動きに対して効果的です。
欠点:
フレームレートに依存するため、デバイスによって動きが異なる可能性があります。
急激な動きに対しては精度が落ちる可能性があります。
累積誤差が生じる可能性があり、長時間の操作で精度が低下することがあります。

3ビューポートスペースでの移動:

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;

[RequireComponent(typeof(Camera))]
public class SceneViewCamera_3 : MonoBehaviour
{
	[SerializeField] private float pinchSpeed = 0.1f;
	[SerializeField] private float dragThreshold = 5f;
	[SerializeField] private float dragSpeed = 1f;

	private Vector2 touchStartPos;
	private float initialFingersDistance;
	private Vector3 initialCameraPosition;
	private bool isPinching = false;
	private int? activeTouchId = null;
	private bool canMoveCamera = false;

	private Camera mainCamera;
	private Transform cameraTransform;

	private RaycastHit[] raycastHitCache = new RaycastHit[1];
	private Dictionary<int, Vector2> touchPositions = new Dictionary<int, Vector2>();

	private void Start()
	{
		mainCamera = Camera.main;
		cameraTransform = transform;
	}

	private void Update()
	{
		TouchUpdate();
	}

	private void TouchUpdate()
	{
		if (Input.touchCount == 1)
		{
			HandleSingleTouch(Input.GetTouch(0));
		}
		else if (Input.touchCount == 2)
		{
			HandlePinch(Input.GetTouch(0), Input.GetTouch(1));
		}
		else
		{
			ResetTouchState();
		}
	}

	private void HandleSingleTouch(Touch touch)
	{
		switch (touch.phase)
		{
			case TouchPhase.Began:
				if (!activeTouchId.HasValue)
				{
					activeTouchId = touch.fingerId;
					touchStartPos = touch.position;
					touchPositions[touch.fingerId] = touch.position;
					canMoveCamera = CheckIfCanMoveCamera(touch.position);
					Debug.Log($"Touch began: canMoveCamera = {canMoveCamera}");
				}
				break;

			case TouchPhase.Moved:
				if (activeTouchId == touch.fingerId && canMoveCamera)
				{
					TouchDrag(touch);
					Debug.Log("Camera moved");
				}
				break;

			case TouchPhase.Ended:
			case TouchPhase.Canceled:
				if (activeTouchId == touch.fingerId)
				{
					ResetTouchState();
				}
				break;
		}
	}

	private void HandlePinch(Touch touch1, Touch touch2)
	{
		if (touch1.phase == TouchPhase.Began || touch2.phase == TouchPhase.Began)
		{
			initialFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			initialCameraPosition = cameraTransform.position;
			isPinching = true;
		}
		else if ((touch1.phase == TouchPhase.Moved || touch2.phase == TouchPhase.Moved) && isPinching)
		{
			float currentFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			float scaleFactor = initialFingersDistance / currentFingersDistance;
			Vector3 newPosition = initialCameraPosition + (cameraTransform.forward * (1f - scaleFactor) * pinchSpeed);
			cameraTransform.position = newPosition;
		}
		else if ((touch1.phase == TouchPhase.Ended || touch2.phase == TouchPhase.Ended) && isPinching)
		{
			isPinching = false;
		}
	}

	private bool CheckIfCanMoveCamera(Vector2 touchPos)
	{
		if (EventSystem.current.IsPointerOverGameObject(activeTouchId.Value)) return false;

		if (TouchUtility.TryGetTouchedObject(out GameObject touchedObject, layerMask: LayerMask.GetMask("Ground", "EggCollider")))
		{
			return touchedObject.CompareTag("Ground");
		}
		return false;
	}

	private void TouchDrag(Touch touch)
	{
		if (!canMoveCamera) return;

		Vector2 viewportDelta = mainCamera.ScreenToViewportPoint(touch.position) - mainCamera.ScreenToViewportPoint(touchStartPos);

		// カメラの右方向と上方向のベクトルを取得
		Vector3 cameraRight = cameraTransform.right;
		Vector3 cameraUp = cameraTransform.up;

		// 移動量を計算(x軸とy軸を反転し、y軸の符号を変更)
		Vector3 move = (-viewportDelta.x * cameraRight - viewportDelta.y * cameraUp) * dragSpeed;

		// カメラの位置を更新
		cameraTransform.position += move;

		Debug.Log($"Viewport delta: {viewportDelta}, Movement: {move}");

		touchStartPos = touch.position;
	}

	private void ResetTouchState()
	{
		activeTouchId = null;
		isPinching = false;
		touchPositions.Clear();
		canMoveCamera = false;
	}
}

ビューポート座標を使用して、画面サイズに依存しない移動を実現します。
利点:
画面サイズに依存しない一貫した動きを提供します。
異なる解像度のデバイスでも同じ感覚で操作できます。
スケーリングが容易で、カメラの動きの調整が簡単です。
欠点:
ワールド座標系との変換が必要で、やや複雑になる可能性があります。
3D空間での深度の違いによる見かけの移動速度の差を考慮する必要があります。
特定のシーン構造に依存する場合、調整が必要になることがあります。

4補間を使用したスムーズな移動:

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;

[RequireComponent(typeof(Camera))]
public class SceneViewCamera_4 : MonoBehaviour
{
	[SerializeField] private float pinchSpeed = 0.1f;
	[SerializeField] private float dragThreshold = 5f;
	[SerializeField] private float dragSpeed = 5f;
	[SerializeField] private float smoothTime = 0.2f; // スムージング時間

	private Vector2 touchStartPos;
	private float initialFingersDistance;
	private Vector3 initialCameraPosition;
	private bool isPinching = false;
	private int? activeTouchId = null;
	private bool canMoveCamera = false;

	private Camera mainCamera;
	private Transform cameraTransform;

	private RaycastHit[] raycastHitCache = new RaycastHit[1];
	private Dictionary<int, Vector2> touchPositions = new Dictionary<int, Vector2>();

	private Vector3 targetPosition;
	private Vector3 velocity = Vector3.zero;

	private void Start()
	{
		mainCamera = Camera.main;
		cameraTransform = transform;
		targetPosition = cameraTransform.position;
	}

	private void Update()
	{
		TouchUpdate();
		SmoothCameraMovement();
	}

	private void TouchUpdate()
	{
		if (Input.touchCount == 1)
		{
			HandleSingleTouch(Input.GetTouch(0));
		}
		else if (Input.touchCount == 2)
		{
			HandlePinch(Input.GetTouch(0), Input.GetTouch(1));
		}
		else
		{
			ResetTouchState();
		}
	}

	private void HandleSingleTouch(Touch touch)
	{
		switch (touch.phase)
		{
			case TouchPhase.Began:
				if (!activeTouchId.HasValue)
				{
					activeTouchId = touch.fingerId;
					touchStartPos = touch.position;
					touchPositions[touch.fingerId] = touch.position;
					canMoveCamera = CheckIfCanMoveCamera(touch.position);
					Debug.Log($"Touch began: canMoveCamera = {canMoveCamera}");
				}
				break;

			case TouchPhase.Moved:
				if (activeTouchId == touch.fingerId && canMoveCamera)
				{
					TouchDrag(touch);
					Debug.Log("Camera moved");
				}
				break;

			case TouchPhase.Ended:
			case TouchPhase.Canceled:
				if (activeTouchId == touch.fingerId)
				{
					ResetTouchState();
				}
				break;
		}
	}

	private void HandlePinch(Touch touch1, Touch touch2)
	{
		if (touch1.phase == TouchPhase.Began || touch2.phase == TouchPhase.Began)
		{
			initialFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			initialCameraPosition = cameraTransform.position;
			isPinching = true;
		}
		else if ((touch1.phase == TouchPhase.Moved || touch2.phase == TouchPhase.Moved) && isPinching)
		{
			float currentFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			float scaleFactor = initialFingersDistance / currentFingersDistance;
			Vector3 newPosition = initialCameraPosition + (cameraTransform.forward * (1f - scaleFactor) * pinchSpeed);
			targetPosition = newPosition;
		}
		else if ((touch1.phase == TouchPhase.Ended || touch2.phase == TouchPhase.Ended) && isPinching)
		{
			isPinching = false;
		}
	}

	private bool CheckIfCanMoveCamera(Vector2 touchPos)
	{
		if (EventSystem.current.IsPointerOverGameObject(activeTouchId.Value)) return false;

		if (TouchUtility.TryGetTouchedObject(out GameObject touchedObject, layerMask: LayerMask.GetMask("Ground", "EggCollider")))
		{
			return touchedObject.CompareTag("Ground");
		}
		return false;
	}

	private void TouchDrag(Touch touch)
	{
		if (!canMoveCamera) return;

		Vector2 viewportDelta = mainCamera.ScreenToViewportPoint(touch.position) - mainCamera.ScreenToViewportPoint(touchStartPos);

		// カメラの右方向と上方向のベクトルを取得
		Vector3 cameraRight = cameraTransform.right;
		Vector3 cameraUp = cameraTransform.up;

		// 移動量を計算(x軸とy軸を反転)
		Vector3 move = (-viewportDelta.x * cameraRight - viewportDelta.y * cameraUp) * dragSpeed;

		targetPosition = cameraTransform.position + move;

		Debug.Log($"Viewport delta: {viewportDelta}, Target movement: {move}");

		touchStartPos = touch.position;
	}

	private void SmoothCameraMovement()
	{
		cameraTransform.position = Vector3.SmoothDamp(cameraTransform.position, targetPosition, ref velocity, smoothTime);
	}

	private void ResetTouchState()
	{
		activeTouchId = null;
		isPinching = false;
		touchPositions.Clear();
		canMoveCamera = false;
	}
}

直接的な移動が急すぎる場合、補間を使用してスムーズな動きを実現できます。
利点:
なめらかで自然な動きを実現できます。
急激な入力に対してもスムーズな反応を示します。
ユーザー体験を向上させ、より洗練された印象を与えます。
欠点:
実際の指の位置とカメラの位置にわずかな遅延が生じます。
補間パラメータの調整が必要で、適切な値を見つけるのに時間がかかる場合があります。
計算量がやや増加し、パフォーマンスに影響を与える可能性があります。

5シンプル移動:

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;

[RequireComponent(typeof(Camera))]
public class SceneViewCamera_0 : MonoBehaviour
{
	[SerializeField] private float moveSpeed = 0.8f;
	[SerializeField] private float pinchSpeed;
	[SerializeField] private float dragThreshold;

	private Vector2 touchStartPos;
	private float initialFingersDistance;
	private Vector3 initialCameraPosition;
	private bool isPinching = false;
	private int? activeTouchId = null;
	private bool canMoveCamera = false;

	private Camera mainCamera;
	private Transform cameraTransform;

	private RaycastHit[] raycastHitCache = new RaycastHit[1];
	private Dictionary<int, Vector2> touchPositions = new Dictionary<int, Vector2>();

	private void Start()
	{
		mainCamera = Camera.main;
		cameraTransform = transform;
	}

	private void Update()
	{
		TouchUpdate();
	}

	private void TouchUpdate()
	{
		if (Input.touchCount == 1)
		{
			HandleSingleTouch(Input.GetTouch(0));
		}
		else if (Input.touchCount == 2)
		{
			HandlePinch(Input.GetTouch(0), Input.GetTouch(1));
		}
		else
		{
			ResetTouchState();
		}
	}

	private void HandleSingleTouch(Touch touch)
	{
		switch (touch.phase)
		{
			case TouchPhase.Began:
				if (!activeTouchId.HasValue)
				{
					activeTouchId = touch.fingerId;
					touchStartPos = touch.position;
					touchPositions[touch.fingerId] = touch.position;
					canMoveCamera = CheckIfCanMoveCamera(touch.position);
					//Debug.Log($"Touch began: {activeTouchId}, Position: {touch.position}");
				}
				break;

			case TouchPhase.Moved:
				if (activeTouchId == touch.fingerId && canMoveCamera)
				{
					float dragDistance = Vector2.Distance(touchStartPos, touch.position);
					if (dragDistance > dragThreshold)
					{
						TouchDrag(touch.position);
					}
					//Debug.Log($"Touch moved: {activeTouchId}, Position: {touch.position}, Distance: {dragDistance}");
				}
				break;

			case TouchPhase.Stationary:
				//Debug.Log($"Touch stationary: {activeTouchId}, Position: {touch.position}");
				break;

			case TouchPhase.Ended:
			case TouchPhase.Canceled:
				if (activeTouchId == touch.fingerId)
				{
					//Debug.Log($"Touch ended/canceled: {activeTouchId}");
					ResetTouchState();
				}
				break;
		}
	}

	private void HandlePinch(Touch touch1, Touch touch2)
	{
		if (touch1.phase == TouchPhase.Began || touch2.phase == TouchPhase.Began)
		{
			initialFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			initialCameraPosition = cameraTransform.position;
			isPinching = true;
		}
		else if ((touch1.phase == TouchPhase.Moved || touch2.phase == TouchPhase.Moved) && isPinching)
		{
			float currentFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			float scaleFactor = initialFingersDistance / currentFingersDistance;
			Vector3 newPosition = initialCameraPosition + (cameraTransform.forward * (1f - scaleFactor) * pinchSpeed);
			cameraTransform.position = newPosition;
		}
		else if ((touch1.phase == TouchPhase.Ended || touch2.phase == TouchPhase.Ended) && isPinching)
		{
			isPinching = false;
		}
	}
	private bool CheckIfCanMoveCamera(Vector2 touchPos)
	{
		if (EventSystem.current.IsPointerOverGameObject(activeTouchId.Value)) return false;

		if (TouchUtility.TryGetTouchedObject(out GameObject touchedObject, layerMask: LayerMask.GetMask("Ground", "EggCollider")))
		{
			return touchedObject.CompareTag("Ground");
		}
		return false;
	}

	private void TouchDrag(Vector2 touchPos)
	{
		if (!canMoveCamera) return;

		Vector2 diff = touchPos - touchStartPos;
		if (diff.sqrMagnitude >= Vector2.kEpsilonNormalSqrt)
		{
			cameraTransform.Translate(-diff * Time.deltaTime * moveSpeed);
			touchStartPos = touchPos;
		}
	}

	private void ResetTouchState()
	{
		activeTouchId = null;
		isPinching = false;
		touchPositions.Clear();
		canMoveCamera = false;
		//Debug.Log("Touch state reset");
	}
}

6結局採用したやつ:

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;

[RequireComponent(typeof(Camera))]
public class ImprovedSceneViewCamera : MonoBehaviour
{
	[SerializeField] private float pinchSpeed = 20f;

	private Vector2 touchStartPos;
	private Vector3 touchWorldStartPos;
	private float initialFingersDistance;
	private Vector3 initialCameraPosition;
	private bool isPinching = false;
	private int? activeTouchId = null;
	private bool canMoveCamera = false;

	private Camera mainCamera;
	private Transform cameraTransform;

	private void Start()
	{
		mainCamera = Camera.main;
		cameraTransform = transform;
	}

	private void Update()
	{
		TouchUpdate();
	}

	private void TouchUpdate()
	{
		if (Input.touchCount == 1)
		{
			HandleSingleTouch(Input.GetTouch(0));
		}
		else if (Input.touchCount == 2)
		{
			HandlePinch(Input.GetTouch(0), Input.GetTouch(1));
		}
		else
		{
			ResetTouchState();
		}
	}

	private void HandleSingleTouch(Touch touch)
	{
		switch (touch.phase)
		{
			case TouchPhase.Began:
				if (!activeTouchId.HasValue)
				{
					activeTouchId = touch.fingerId;
					touchStartPos = touch.position;
					touchWorldStartPos = GetWorldPosition(touchStartPos);
					canMoveCamera = CheckIfCanMoveCamera(touch.position);
				}
				break;

			case TouchPhase.Moved:
				if (activeTouchId == touch.fingerId)
				{
					if (canMoveCamera)
					{
						TouchDrag(touch);
					}
					else
					{
						// ここで他のタッチ処理を行う(必要に応じて)
					}
				}
				break;

			case TouchPhase.Ended:
			case TouchPhase.Canceled:
				if (activeTouchId == touch.fingerId)
				{
					ResetTouchState();
				}
				break;
		}
	}

	private void HandlePinch(Touch touch1, Touch touch2)
	{
		if (touch1.phase == TouchPhase.Began || touch2.phase == TouchPhase.Began)
		{
			initialFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			initialCameraPosition = cameraTransform.position;
			isPinching = true;
		}
		else if ((touch1.phase == TouchPhase.Moved || touch2.phase == TouchPhase.Moved) && isPinching)
		{
			float currentFingersDistance = Vector2.Distance(touch1.position, touch2.position);
			float scaleFactor = initialFingersDistance / currentFingersDistance;
			Vector3 newPosition = initialCameraPosition + (cameraTransform.forward * (1f - scaleFactor) * pinchSpeed);
			cameraTransform.position = newPosition;
		}
		else if ((touch1.phase == TouchPhase.Ended || touch2.phase == TouchPhase.Ended) && isPinching)
		{
			isPinching = false;
		}
	}

	private bool CheckIfCanMoveCamera(Vector2 touchPos)
	{
		if (EventSystem.current.IsPointerOverGameObject(activeTouchId.Value)) return false;

		if (TouchUtility.TryGetTouchedObject(out GameObject touchedObject, layerMask: LayerMask.GetMask("Ground", "EggCollider", "GetWorks")))
		{
			return touchedObject.CompareTag("Ground");
		}

		return false;
	}

	private void TouchDrag(Touch touch)
	{
		if (!canMoveCamera) return;

		Vector3 currentTouchWorldPos = GetWorldPosition(touch.position);
		Vector3 worldSpaceDelta = touchWorldStartPos - currentTouchWorldPos;

		// カメラの位置を更新
		cameraTransform.position += worldSpaceDelta;
	}

	private Vector3 GetWorldPosition(Vector2 screenPosition)
	{
		Ray ray = mainCamera.ScreenPointToRay(screenPosition);
		Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
		if (groundPlane.Raycast(ray, out float distance))
		{
			return ray.GetPoint(distance);
		}
		return Vector3.zero;
	}

	private void ResetTouchState()
	{
		activeTouchId = null;
		isPinching = false;
		canMoveCamera = false;
	}
}

AIにiPhoneの写真アプリみたいにって言ったら、これ出してきた。

というか、エディタ上でUnity Remoteで動かすときと、Buildして実機で動かすときとじゃ、全然挙動が違う。それ知ってればサクッとできたのに。というお話。

待機処理3種紹介+α

Unityで使うC#プログラムでのそれぞれの待機処理の特徴や違い



コルーチン

using UnityEngine;
using System.Collections;

public class MyScript : MonoBehaviour
{
    // コルーチンを呼び出す関数
    void Start()
    {
        StartCoroutine(MyCoroutine());
    }

    // コルーチン関数
    IEnumerator MyCoroutine()
    {
        // 何かしらの処理
        Debug.Log("処理開始");

        // 2秒間待つ
        yield return new WaitForSeconds(2f);

        // コルーチンを終了
        yield break;
    }
}
  • 柔軟
  • using System.Collections;が必要
  • 個人的に読みにくい
  • StartCoroutine(MyCoroutine());←文字列じゃなくて関数で呼び出したほうが便利
  • コルーチン中にデストロイされると、処理が強制終了



Invoke

using UnityEngine;

public class MyScript : MonoBehaviour
{
    void Start()
    {
        // 2秒後にMyFunction関数を呼び出す
        Invoke("MyFunction", 2f);
    }

    void MyFunction()
    {
        Debug.Log("2秒後に実行されます");
    }
}
  • つづり覚えれない(Inボケ)
  • シンプル→引数使えない(単純に待つのみ)
  • デストロイされても、復活できる
  • Invoke(“MyFunction”, 2f);→文字列で渡すのでタイプミス注意



DOTween

using UnityEngine;
using DG.Tweening;

public class MyScript : MonoBehaviour
{
    void Start()
    {
        // 2秒後にMyFunctionを実行
        DOVirtual.DelayedCall(2f, () => MyFunction());
    }

    void MyFunction()
    {
        Debug.Log("2秒後に実行されました");
    }
}
  • コードがシンプルでよみやすい
  • ほかにも色んなことできる
  • 負荷はおなじぐらい
  • using DG.Tweening;が必要
  • パッケージマネジャーからインストールが必要
  • デストロイされるとエラーになる



UniTask

using Cysharp.Threading.Tasks;

async UniTask StartAsync()
{
    // 2秒待つ
    await UniTask.Delay(TimeSpan.FromSeconds(2));

    Debug.Log("2秒経過しました");
}
  • 使ったことないが、最新式で軽量でおすすめらしい