Files
MobileCameraShare/Assets/Scripts/MobileCameraStarter.cs
HIRO_MAC 1911637020 Add mobile camera capture, polaroid preview, gallery save & native share
- カメラ撮影 → プレビュー(ポラロイド風白縁)→ 保存 / ネイティブ共有
- iOS / Android のネイティブ実装(共有・ギャラリー保存・FileProvider)
- iOS は共有シートのプリウォームで初回表示を軽減
- iOS Info.plist 用途説明をビルド後処理で自動付与
- Android パッケージ名ガードを有効化(ビルド前チェック)
- README を整備、.gitignore に slnx / .vscode / .utmp を追加

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:56:15 +09:00

517 lines
14 KiB
C#

using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
#if UNITY_ANDROID
using UnityEngine.Android;
#endif
public class MobileCameraStarter : MonoBehaviour
{
[Header("カメラ映像を表示する RawImage")]
public RawImage cameraView;
[Header("シェアボタンのGameObject")]
public GameObject shareButtonObject;
[Header("シェアボタンのOnClickを自動登録する")]
public bool autoAddShareButtonOnClick = true;
[Header("キャプチャ時にシェアボタンを隠す")]
public bool hideShareButtonWhileCapturing = true;
[Header("前面カメラを使う")]
public bool useFrontCamera = false;
[Header("希望する解像度とFPSを使う")]
public bool useRequestedResolution = true;
[Header("希望する解像度とFPS")]
public int requestedWidth = 1280;
public int requestedHeight = 720;
public int requestedFPS = 30;
[Header("シェア設定")]
public string shareText = "カメラ画像をシェアします";
public string shareSubject = "Camera Screenshot";
public string shareFilePrefix = "camera_share";
[Header("プレビュー用 Canvas_preview プレハブ (未設定なら Resources から読み込み)")]
public GameObject canvasPreviewPrefab;
[Header("キャプチャ時に一時的に隠すUI")]
public GameObject[] hideWhileCapturing;
private WebCamTexture webCamTexture;
private AspectRatioFitter aspectRatioFitter;
private Button shareButton;
private bool isStartingCamera = false;
private bool isCapturing = false;
private bool isCaptureReady = false;
private int lastWidth = -1;
private int lastHeight = -1;
private int lastRotation = -1;
private bool lastMirrored = false;
#if UNITY_ANDROID
private PermissionCallbacks permissionCallbacks;
#endif
private IEnumerator Start()
{
SetupCameraView();
SetupShareButton();
SetShareButtonInteractable(false);
yield return RequestCameraPermission();
}
private void SetupCameraView()
{
if (cameraView == null)
{
Debug.LogWarning("Camera View に RawImage が設定されていません。");
return;
}
aspectRatioFitter = cameraView.GetComponent<AspectRatioFitter>();
if (aspectRatioFitter == null)
{
aspectRatioFitter = cameraView.gameObject.AddComponent<AspectRatioFitter>();
}
aspectRatioFitter.aspectMode = AspectRatioFitter.AspectMode.EnvelopeParent;
// カメラ背景として使うだけならタッチ判定は不要
cameraView.raycastTarget = false;
}
private void SetupShareButton()
{
if (shareButtonObject == null)
{
Debug.LogWarning("Share Button Object が設定されていません。");
return;
}
shareButton = shareButtonObject.GetComponent<Button>();
if (shareButton == null)
{
Debug.LogWarning("Share Button Object に Button コンポーネントがありません。");
return;
}
if (autoAddShareButtonOnClick)
{
shareButton.onClick.RemoveListener(CapturePreview);
shareButton.onClick.AddListener(CapturePreview);
}
}
private void SetShareButtonInteractable(bool value)
{
if (shareButton != null)
{
shareButton.interactable = value;
}
}
private IEnumerator RequestCameraPermission()
{
#if UNITY_IOS
yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);
if (Application.HasUserAuthorization(UserAuthorization.WebCam))
{
StartCoroutine(StartCamera());
}
else
{
Debug.LogWarning("iOS: カメラの使用が許可されませんでした。");
SetShareButtonInteractable(false);
}
#elif UNITY_ANDROID
if (Permission.HasUserAuthorizedPermission(Permission.Camera))
{
StartCoroutine(StartCamera());
}
else
{
permissionCallbacks = new PermissionCallbacks();
permissionCallbacks.PermissionGranted += OnAndroidCameraPermissionGranted;
permissionCallbacks.PermissionDenied += OnAndroidCameraPermissionDenied;
permissionCallbacks.PermissionDeniedAndDontAskAgain += OnAndroidCameraPermissionDeniedAndDontAskAgain;
Permission.RequestUserPermission(Permission.Camera, permissionCallbacks);
}
yield return null;
#else
StartCoroutine(StartCamera());
yield return null;
#endif
}
#if UNITY_ANDROID
private void OnAndroidCameraPermissionGranted(string permissionName)
{
if (permissionName == Permission.Camera)
{
Debug.Log("Android: カメラ権限が許可されました。");
StartCoroutine(StartCamera());
}
}
private void OnAndroidCameraPermissionDenied(string permissionName)
{
Debug.LogWarning("Android: カメラ権限が拒否されました。");
SetShareButtonInteractable(false);
}
private void OnAndroidCameraPermissionDeniedAndDontAskAgain(string permissionName)
{
Debug.LogWarning("Android: カメラ権限が拒否され、今後表示しない設定になりました。");
SetShareButtonInteractable(false);
}
#endif
private IEnumerator StartCamera()
{
if (isStartingCamera)
{
yield break;
}
isStartingCamera = true;
isCaptureReady = false;
SetShareButtonInteractable(false);
if (cameraView == null)
{
Debug.LogWarning("Camera View に RawImage が設定されていません。");
isStartingCamera = false;
yield break;
}
StopCamera();
yield return null;
WebCamDevice[] devices = WebCamTexture.devices;
Debug.Log("使用可能なカメラ数: " + devices.Length);
for (int i = 0; i < devices.Length; i++)
{
Debug.Log(
"Camera[" + i + "] " +
devices[i].name +
" / Front: " + devices[i].isFrontFacing
);
}
if (devices.Length == 0)
{
Debug.LogWarning("使用できるカメラが見つかりません。");
isStartingCamera = false;
SetShareButtonInteractable(false);
yield break;
}
string selectedCameraName = devices[0].name;
for (int i = 0; i < devices.Length; i++)
{
if (devices[i].isFrontFacing == useFrontCamera)
{
selectedCameraName = devices[i].name;
break;
}
}
Debug.Log("使用するカメラ: " + selectedCameraName);
if (useRequestedResolution)
{
webCamTexture = new WebCamTexture(
selectedCameraName,
requestedWidth,
requestedHeight,
requestedFPS
);
}
else
{
// Androidで黒画面になる場合は、まずこちらを試す
webCamTexture = new WebCamTexture(selectedCameraName);
}
cameraView.texture = webCamTexture;
webCamTexture.Play();
float timeout = 5f;
while ((webCamTexture.width <= 16 || webCamTexture.height <= 16) && timeout > 0f)
{
timeout -= Time.deltaTime;
yield return null;
}
Debug.Log(
"WebCamTexture width: " + webCamTexture.width +
" / height: " + webCamTexture.height +
" / rotation: " + webCamTexture.videoRotationAngle +
" / mirrored: " + webCamTexture.videoVerticallyMirrored
);
if (webCamTexture.width <= 16 || webCamTexture.height <= 16)
{
Debug.LogWarning("カメラ映像のフレームが取得できていません。");
isStartingCamera = false;
isCaptureReady = false;
SetShareButtonInteractable(false);
yield break;
}
ApplyCameraTextureSettings();
isStartingCamera = false;
isCaptureReady = true;
SetShareButtonInteractable(true);
Debug.Log("カメラを開始しました。キャプチャ準備完了。");
}
private void Update()
{
if (webCamTexture == null || !webCamTexture.isPlaying)
{
return;
}
if (
webCamTexture.width != lastWidth ||
webCamTexture.height != lastHeight ||
webCamTexture.videoRotationAngle != lastRotation ||
webCamTexture.videoVerticallyMirrored != lastMirrored
)
{
ApplyCameraTextureSettings();
}
}
private void ApplyCameraTextureSettings()
{
if (cameraView == null || webCamTexture == null)
{
return;
}
int rotationAngle = webCamTexture.videoRotationAngle;
cameraView.rectTransform.localEulerAngles =
new Vector3(0f, 0f, -rotationAngle);
if (webCamTexture.videoVerticallyMirrored)
{
cameraView.uvRect = new Rect(0f, 1f, 1f, -1f);
}
else
{
cameraView.uvRect = new Rect(0f, 0f, 1f, 1f);
}
float aspectRatio = (float)webCamTexture.width / webCamTexture.height;
if (aspectRatioFitter != null)
{
aspectRatioFitter.aspectRatio = aspectRatio;
aspectRatioFitter.aspectMode = AspectRatioFitter.AspectMode.EnvelopeParent;
}
lastWidth = webCamTexture.width;
lastHeight = webCamTexture.height;
lastRotation = webCamTexture.videoRotationAngle;
lastMirrored = webCamTexture.videoVerticallyMirrored;
}
// 旧名互換: ボタンの onClick が CaptureAndShare を参照していても動くようにする
public void CaptureAndShare()
{
CapturePreview();
}
public void CapturePreview()
{
if (!isCaptureReady)
{
Debug.LogWarning("まだキャプチャの準備ができていません。");
return;
}
if (isCapturing)
{
return;
}
StartCoroutine(CaptureAndPreviewCoroutine());
}
private IEnumerator CaptureAndPreviewCoroutine()
{
isCapturing = true;
SetShareButtonInteractable(false);
bool shareButtonPreviousActive = true;
if (shareButtonObject != null && hideShareButtonWhileCapturing)
{
shareButtonPreviousActive = shareButtonObject.activeSelf;
shareButtonObject.SetActive(false);
}
bool[] previousActiveStates = null;
if (hideWhileCapturing != null && hideWhileCapturing.Length > 0)
{
previousActiveStates = new bool[hideWhileCapturing.Length];
for (int i = 0; i < hideWhileCapturing.Length; i++)
{
if (hideWhileCapturing[i] != null)
{
previousActiveStates[i] = hideWhileCapturing[i].activeSelf;
hideWhileCapturing[i].SetActive(false);
}
}
}
yield return new WaitForEndOfFrame();
Texture2D screenshot = new Texture2D(
Screen.width,
Screen.height,
TextureFormat.RGB24,
false
);
screenshot.ReadPixels(
new Rect(0f, 0f, Screen.width, Screen.height),
0,
0
);
screenshot.Apply();
byte[] pngData = screenshot.EncodeToPNG();
string fileName =
shareFilePrefix + "_" +
System.DateTime.Now.ToString("yyyyMMdd_HHmmss") +
".png";
string filePath = Path.Combine(Application.temporaryCachePath, fileName);
File.WriteAllBytes(filePath, pngData);
// screenshot はここでは破棄しない。プレビュー表示に使い、
// ダイアログを閉じる時に CanvasPreview 側で破棄する。
if (hideWhileCapturing != null && previousActiveStates != null)
{
for (int i = 0; i < hideWhileCapturing.Length; i++)
{
if (hideWhileCapturing[i] != null)
{
hideWhileCapturing[i].SetActive(previousActiveStates[i]);
}
}
}
if (shareButtonObject != null && hideShareButtonWhileCapturing)
{
shareButtonObject.SetActive(shareButtonPreviousActive);
}
Debug.Log("プレビュー用画像を保存しました: " + filePath);
// 撮影が終わったらプレビューダイアログを開く。
// 保存 / シェアの実行はダイアログ側のボタンから行う。
CanvasPreview preview = CanvasPreview.Show(
filePath,
screenshot,
shareText,
shareSubject,
true, // テクスチャの所有権をプレビューに渡す
OnPreviewClosed, // 閉じたら撮影ボタンを戻す
canvasPreviewPrefab
);
if (preview == null)
{
// プレビューを開けなかった場合のフォールバック
Destroy(screenshot);
isCapturing = false;
if (isCaptureReady)
{
SetShareButtonInteractable(true);
}
yield break;
}
// プレビュー表示中はボタンを無効のままにし、
// 閉じられた時に OnPreviewClosed で復帰させる。
isCapturing = false;
}
private void OnPreviewClosed()
{
if (isCaptureReady)
{
SetShareButtonInteractable(true);
}
}
public void StopCamera()
{
isCaptureReady = false;
SetShareButtonInteractable(false);
if (webCamTexture != null)
{
if (webCamTexture.isPlaying)
{
webCamTexture.Stop();
}
webCamTexture = null;
}
if (cameraView != null)
{
cameraView.texture = null;
}
}
private void OnDestroy()
{
StopCamera();
if (shareButton != null && autoAddShareButtonOnClick)
{
shareButton.onClick.RemoveListener(CapturePreview);
}
}
}