表示: 1 - 7 of 7 結果

UnityでiOS用にビルドする方法

作ったゲームをiOSで遊べるようにAppStoreに並べるためには、macを使ってXcodeで書き出す必要があります。今回で二回目だが、はじめてやった時同様、苦戦したので議事録として残しておきます。

基本的には上記の記事が参考になります。丁寧に図付きで説明されてるので、ひっかかることなく進めると思います。

以前「なまつか」をリリースしたときには、問題なく行えました。が、今回macOS Sequoia(セコイア)15.0.1にアップデートしたらできなくなりました。今回、私がつまずいた所を抜粋します。


macOSのアプリ、キーチェーンアクセスを起動します。

キーチェーンアプリの起動場所がアプリケーションから変更になりました。

キーチェーンアクセス

キーチェーンアクセスは従来のアプリケーション>ユーティリティじゃなくて、CoreServices>アプリケーションに移動してます。spotlightで検索すればすぐ出てきます。


Provisioning Profileをダブルクリックすると、自動でmacのキーチェーンに登録

「Download」を押し、Linble_Keyboard.mobileprovisionをダウンロードします。ダウンロードした.mobileprovisionファイルをダブルクリックすると、自動でmacのキーチェーンに登録してくれます。

ダブルクリックしたらXcodeが開いてしまい、キーチェーンに登録できません。仕方がないのでドラッグして登録しようとするも、エラーになります。

ネットには色々な登録方法がみつかりますが、私は次の方法が一番分かりやすかったです。(しかしこれを行ったからBuildが成功したかどうか正直微妙です。)

Provisioning ProfileをXcodeに設定します。まずは適当な場所にダウンロードしておきます。

023.png

Provisioning Profileダウンロード

ダウンロードしたらSigning & Capabilitiesの設定でAutomatically manage Signingのチェックを外します。

Provisioning Profileのプルダウンが選択できるので、先ほどダウンロードしたファイルを指定します。


Xcodeで開くファイル

Unity-iPhone.xcworkspace と Unity-iPhone.xcodeproj ふたつあるのですが、Unity-iPhone.xcworkspace が正解です。こちらは広告等のデータが入っています。iPhone.xcworkspaceがない場合はUnity-iPhone.xcodeprojでOK。


毎回言語設定をするのが面倒

UnityでBuildしてから、Xcodeで開いてそのまま書き出すと、言語が英語になります。面倒なので、Unity側で自動で設定してしまいましょう。

上記サイトに従って、設定すればOK。成功するとUnityでBuildしただけで自動的に日本語になります。


Xcodeで開いたらここだけ変更してBuildしましょう

Unity-iPhone の Signing & CapabilitiesタブでTeamを変更すればOK。Provisioning Profileが正しく設定されていれば、ここだけ変更してBuildすると成功します。

App Store用に書き出すにはProduct > Archive から書き出せます。

書き出したら、成功しているかチェック(Validate)してからアップロードすればOK。

この画面になればOK。あとはApp Store Connectにアクセスしてアプリから目的のアプリを選べばOK。反映するまで数分かかりますが、ビルドの横に+ボタンが出来てるはず。


プライバシーの設定

こちらのサイトが参考になります。


おつかれさまでした。いっしょにがんばっていきましょい。

カメラ移動スクリプト

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して実機で動かすときとじゃ、全然挙動が違う。それ知ってればサクッとできたのに。というお話。

素材集のブックマーク

突然ですが、私のブラウザに保存されている素材集のブックマークを共有いたします。
誰かの参考になれば幸いです。
めんどくさいので、ガサッとコピペしただけで申し訳無いです...

ニコニ・コモンズ
「紙」の画像素材を無料ダウンロード(1)背景フリー素材 BEIZ images
滑らかな紙の粒子 | 無料テクスチャ
Free Content
フリーBGM|無料ゲーム音楽専門ダウンロード-ユーフルカ
フリーBGMサイト紹介 – 一般社団法人フリーBGM協会
多彩なコンセプトのフリー音楽素材 BGM「音の園」
Soundful
多彩なコンセプトのフリー音楽素材 BGM「音の園」
フリーBGM素材『なんでしょう?』試聴ページ|フリーBGM DOVA-SYNDROME
閲覧 | スプライス
ホーム – フラッシュバックジャパン
たうろんの!成りあ音楽! | 音を選ぶ楽しさ、きっとみつかる。多分₍⁠₍⁠ ⁠◝⁠(⁠ ゚⁠∀⁠ ゚⁠ ⁠)⁠◟⁠ ⁠⁾⁠⁾
PSDファイル アーカイブ | PhotoshopVIP
商用利用OKなフリー動画素材配布サイトおすすめ15選!
エフェクトの動画|無料の動画素材サイト「動画AC」
PSDファイル アーカイブ | PhotoshopVIP
YouTubeから変換MP3 – オンラインコンバータ
無料の動画素材サイト|動画AC
200,000+件の最高の鳥のさえずり関連動画 · 100%無料でダウンロード · Pexelのストック動画
YouTuberのための素材屋さん
4000無料動画素材, 著作権フリーストックビデオ
効果音ラボ – フリー、商用無料、報告不用の効果音素材をダウンロード
App Privacy Policy Generator
ボタン・システム音[1]|効果音ラボ
フリーBGM 「あと10分。そしたらセーブして消すから(just10more minutes to go)」/Lo-Fiオルゴール&8bit/free/かわいい/kawaii/cute/作業勉強用 – ねこのまきのBGM – BOOTH
Cosmic Media
BGM🎵 | たうろんの!成りあ音楽!
ボイス(音声)素材 – フリーBGM|無料ゲーム音楽 ユーフルカ
4.400万点以上の高品質なフリー画像素材 – Pixabay – Pixabay
ぱくたそ – 会員登録不要、無料の写真素材・AI画像素材
無料イラスト・フリー素材なら「イラストAC」
武器素材 破滅の杖-シェア・マテリアル
「北欧風の素朴な作りの木製額縁」の画像・写真素材を無料ダウンロード(1)背景フリー素材 BEIZ images
Unityの素材データ 人気の同人グッズ442点を通販!話題のアニメやデザインから個性的で被らないアイテムが見つかる – BOOTH
8ページ目 – Unityの素材データ 人気の同人グッズ442点を通販!話題のアニメやデザインから個性的で被らないアイテムが見つかる – BOOTH
Effekseerで作ったエフェクトをUnityで使う|hirokichigamer804
コツコツエンジニアノート
VST プラグイン (無料ダウンロード) | 99音
AzCt Laboratory: » Material Top (利用規約)
【フリーBGM】Sweet Pop Sweets / 甘くてポップなお菓子【1時間耐久】 kawaii BGM 配信 雑談 vlog – YouTube

TextMeshPro

TextMeshProの謎の枠

Unityでテキストを扱う際にTextMeshProを使用すると思いますが、画像のように枠が2つ表示されます。これ実は重要なんです。



1つ目は黄色で表示される枠線


黄色の枠線は、インスペクターでみると左の画像のあたりが関係してきます。

Alignment の項目で黄色の枠線の中央に表示するか、または左揃えにするか、等を変更することができます。

枠線からはみ出した場合の処理は、
Wrapping や Overflow で変更できます。

2つ目の四隅に青い丸がある枠線


問題になるのは、外側の方の四隅に青い丸がある枠線です。

Rect Transform の Width と Height がその大きさなのですが、うっかり大きくしていても気付かない時があります。

大きくても問題ないと思ったそこのアナタ。これ透明で描写処理されています。


レンダリングの際に透明として描写される

つまりその分GPUに負荷がかかっています。透明の描写って結構重いんです。
何に気をつけないといけないかというとこの枠線がはるか遠くまで巨大に広がっている場合

左は一見すると大丈夫そうですが、カメラをひいてみると右のようになってます。
つまり、超巨大な透明をわざわざ描写しています。しかもこれ一つならまだしも、このオブジェクトをコピーして別の文字に変えて作った場合。

透明の画像を何枚も何枚も描写してしまっています。シーンビューならまだしも、ゲームビューで見てもさっぱりわかりません。これはなかなか気づきません。

2つ目の枠の設定するところ

TextMeshPro – Text (UI)コンポーネントの下のほうにある Extra Settings を開くとそこに Margins という項目があって、これが上下左右の隙間の値です。

おまけにこの枠には Raycast Target の項目があります。
横に置いたボタンがなぜかクリックできない場合、この透明の板の向こう側にボタンがある可能性があります。

以上、クリックできないボタンと格闘し、なぜか描写に時間がかかる罠にハマった人の覚書でした。

待機処理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秒経過しました");
}
  • 使ったことないが、最新式で軽量でおすすめらしい

なまちゃんと愉快な仲間たち

うちに住んでいる同居人を紹介します。まずはなまちゃん。
白文鳥で『なまなま』『なまつか』のゲームの主役でもあります。

美人ですが、性格は悪いです。なまちゃんについては、これからいっぱい出てきますので、サラッとご紹介。
グッツ展開もしています。minneというサイトで販売もしてますよ。

作ってる人のX(Twitter)アカウント
作ってる人のホームページ(KANAKO TAKEUCHI)



ツチガエルの”ロボ” と フタホシコオロギの”たかし”

夏に川遊び行った時に捕まえました。何歳か分からないけど、捕まえてからもう5年ほど経ちました。意外と長生きです。全くなついてくれません。水換えのたびに飛び出して家中をかけまわります。とてもグルメなので人工餌は食べません。

”ロボ”と同時に、ロボをアリぐらいに小さくした”チビ”も一緒に捕まえましたが、ご飯を食べてくれず亡くなりました。

たかしは近所のペットショップで買ってきます。”たかしA”と”たかしB”と”たかしC”とたか... 生まれ変わります。一匹20円。ヨーロッパイエコオロギと悩むのですが、フタホシ派です。ふとしたときに何故か家の中を歩き回ってます。秋には大合唱です。生育環境を確立させるまですぐ死ぬので大変でした。



昔いた生き物たち


アイリッシュセッターの”たー”

”たー”がいた頃は、生活が”たー”中心にまわってました。毛並みがキレイで大型犬なので、近所でも有名だったようです。”たー”に会いに近所の小学生がよく遊びにきてました。一度アイリッシュセッターを飼うと、もうほかの犬種には目がいかないです。犬がいる生活は楽しかったです。また飼いたいのですが今はちょっと厳しいです。でもまた大型犬飼いたいな。
他にもボルゾイかサルーキが好きです。やっぱり犬はマズルが長くてなんぼですw

ミニチュアダックスフンドの”ぬー”

先住犬の”ぬー”です。2人を散歩させてたら、よく親子と言われてました。いい感じのマズルの長さです。犬は最高の相棒です。今はいないので寂しいです。



ヒメウズラの”たまちゃん”

タマゴから温めて生まれてきました。なまちゃんは”たまちゃん”の次の子だから、『た行』の次の『な行』からとって”なまちゃん”なんです。



ほかにも紹介しきれない子たちがいっぱい

セキセイインコにうずら、イモリやら何やらいっつも動物に囲まれて過ごしています。



最後に庭のねむの木の紹介

十年以上前にお隣さんからねむの木の種をいただきました。はじめはタンポポより小さかったんです。それが今では背丈を超えて、花まで咲かせるようになりました。10月の今週に5匹の蝶(キチョウ)が蛹をつくって飛び立っていきました。
抜いても抜いても生えてくる雑草は、名前を付けて逆に育てています。その名は”ZASSO”。昨日、花が咲きました。”ZASSO”も色んな種類があって、愛おしいです。

なまなま ―なまちゃんが生まれる―

はじめまして

これから、投稿を始めていきます。X(Twitter)で文字数が足りなくて載せれない時、不意にいいアイディアが思いついた時、共有したい情報、手法、Tips。

皆さんと一緒に続けていければ最高だと考えてます。
動画配信とともに、少しづつ伸びていって欲しい。よろしくお願いします。

Unityを触り始めて半年が過ぎました。そしてようやく二作目の『なまなま』がリリースできそうです。
しかしそれを阻む、”モバイル端末の最適化”。そうです。重すぎて動かないのです。でもこうやって配信を続けて、SNSで繋がった方々に支えられてなんとかなりそうです。もしこれが本当に一人なら、挫折していたでしょう。でも違います。私達は一人じゃないです。なにせ”マルチ集団”ですので!

なまなま
なまなま

1920px 1080pxで作っています。もしよかったらスマホの壁紙にでもしてもらえたら喜びます。