[Unity] UGI でデバイスごとの画面の比率違いに対応する


もっと便利に

少し前に書いたこちらで、

Canvas設定について書いたのでこれで十分なのですが・・・
もう少し便利になるように、Canvas用のコンポーネントを作りました。
右往左往した結果、~3とかになってしまっていますけど、気にしてはいけません。
作ったばかりなのでバグもあったり、意図通り動かなかったりするかもしれませんが、気にしてはいけません。

動作させるとこんな感じ







パラメーターとか

Target Area キャンバス直下に作ったゲームオブジェクトを登録して、そのオブジェクトの下位にUIを配置していくことを想定しています。
Reference Resolution"Canvas Scaler"と同じです。
Safe Rect広告枠の確保とかで使えます。720x1150にして130のSafe Rectを設定するとかです。
残りは・・・上の動画をみて興味があったら実際に使ってみてください!
リクエストがあれば、また改めて詳しく説明したり?

ソースコード

無駄に長いよ。
※ファイルは2つあります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class SafeAreaHelper
{
/// <summary>
/// アンカー
/// </summary>
public enum Anchor
{
/// <summary>
/// 左側
/// </summary>
Left,
/// <summary>
/// 右側
/// </summary>
Right,
/// <summary>
/// 上側
/// </summary>
Top,
/// <summary>
/// 下側
/// </summary>
Bottom,
}
/// <summary>
/// RectOffsetの簡易Float版
/// </summary>
public class RectFloatOffset
{
/// <summary>
/// 左側
/// </summary>
public float left;
/// <summary>
/// 右側
/// </summary>
public float right;
/// <summary>
/// 上側
/// </summary>
public float top;
/// <summary>
/// 下側
/// </summary>
public float bottom;
/// <summary>
/// 水平
/// </summary>
public float Horizontal { get { return left + right; } }
/// <summary>
/// 垂直
/// </summary>
public float Vertical { get { return top + bottom; } }
/// <summary>
/// コンストラクター
/// </summary>
public RectFloatOffset()
{
left = 0;
right = 0;
top = 0;
bottom = 0;
}
/// <summary>
/// コンストラクター
/// </summary>
/// <param name="targetLeft">左側</param>
/// <param name="targetRight">右側</param>
/// <param name="targetTop">上側</param>
/// <param name="targetBottom">下側</param>
public RectFloatOffset(float targetLeft, float targetRight, float targetTop, float targetBottom)
{
left = targetLeft;
right = targetRight;
top = targetTop;
bottom = targetBottom;
}
}
public static Rect RectTransformToScreen(Canvas canvas, RectTransform targetRectTransform,float scaledDensity)
{
var canvasRect = canvas.GetComponent<RectTransform>();
var scaleX = Screen.width / canvasRect.sizeDelta.x;
var scaleY = Screen.height / canvasRect.sizeDelta.y;
var worldCorners = new Vector3[4];
targetRectTransform.GetWorldCorners(worldCorners);
worldCorners[0] = canvas.transform.InverseTransformPoint(worldCorners[0]);
worldCorners[1] = canvas.transform.InverseTransformPoint(worldCorners[1]);
worldCorners[2] = canvas.transform.InverseTransformPoint(worldCorners[2]);
worldCorners[3] = canvas.transform.InverseTransformPoint(worldCorners[3]);
float x = worldCorners[0].x * scaleX;
float y = worldCorners[1].y * scaleY;
float width = (worldCorners[3].x - worldCorners[0].x) * scaleX;
float height = (worldCorners[1].y - worldCorners[0].y) * scaleY;
x += Screen.width * 0.5f;
y += Screen.height * 0.5f;
y = Screen.height - y;
x /= scaledDensity;
y /= scaledDensity;
width /= scaledDensity;
height /= scaledDensity;
return new Rect(x, y, width, height);
}
public static Rect TransformToScreen(Camera targetCamera, SpriteRenderer targetSprite,float scaledDensity)
{
var worldCenter = targetSprite.transform.position;
var worldSize = targetSprite.transform.lossyScale;
worldSize.x *= targetSprite.sprite.textureRect.width;
worldSize.y *= targetSprite.sprite.textureRect.height;
worldSize.x /= targetSprite.sprite.pixelsPerUnit;
worldSize.y /= targetSprite.sprite.pixelsPerUnit;
var worldCorners = new Vector3[4];
worldCorners[0] = worldCenter;
worldCorners[0].x -= worldSize.x * 0.5f;
worldCorners[0].y -= worldSize.y * 0.5f;
worldCorners[1] = worldCenter;
worldCorners[1].x -= worldSize.x * 0.5f;
worldCorners[1].y += worldSize.y * 0.5f;
worldCorners[2] = worldCenter;
worldCorners[2].x += worldSize.x * 0.5f;
worldCorners[2].y += worldSize.y * 0.5f;
worldCorners[3] = worldCenter;
worldCorners[3].x += worldSize.x * 0.5f;
worldCorners[3].y -= worldSize.y * 0.5f;
worldCorners[0] = RectTransformUtility.WorldToScreenPoint(targetCamera, worldCorners[0]);
worldCorners[1] = RectTransformUtility.WorldToScreenPoint(targetCamera, worldCorners[1]);
worldCorners[2] = RectTransformUtility.WorldToScreenPoint(targetCamera, worldCorners[2]);
worldCorners[3] = RectTransformUtility.WorldToScreenPoint(targetCamera, worldCorners[3]);
float x = worldCorners[0].x;
float y = worldCorners[1].y;
float width = (worldCorners[3].x - worldCorners[0].x);
float height = (worldCorners[1].y - worldCorners[0].y);
y = Screen.height - y;
x /= scaledDensity;
y /= scaledDensity;
width /= scaledDensity;
height /= scaledDensity;
return new Rect(x, y, width, height);
}
}







次にこちら、
コンポーネントのほうです。
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
namespace Chigusa
{
/// <summary>
/// セーフエリアを含んだキャンバス
/// </summary>
[ExecuteInEditMode]
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(Canvas))]
[RequireComponent(typeof(CanvasScaler))]
public class CanvasInSafeArea3 : MonoBehaviour
{
[Tooltip("対象のパネル")]
public RectTransform targetArea;
[Tooltip("基準解像度")]
public Vector2 referenceResolution = new Vector2(720, 1280);
[Tooltip("セーフエリア")]
public RectOffset safeRect;
[Tooltip("セーフオフセット")]
public Vector2 safeOffset;
[Tooltip("セーフハーフオフセット")]
public bool safeHalfOffset;
[Tooltip("アンカー")]
public TextAnchor anchor = TextAnchor.MiddleCenter;
[Tooltip("横への自動ストレッチ")]
public bool stretchWidth = false;
[Tooltip("縦への自動ストレッチ")]
public bool stretchHeight = false;
protected Canvas TargetCanvas { get; set; }
protected RectTransform TargetCanvasRect { get; set; }
protected CanvasScaler TargetCanvasScaler { get; set; }
/// <summary>
/// Awake
/// </summary>
private void Awake()
{
TargetCanvas = GetComponentInParent<Canvas>();
TargetCanvasRect = TargetCanvas.GetComponent<RectTransform>();
TargetCanvasScaler = GetComponentInParent<CanvasScaler>();
}
/// <summary>
/// Start、初期数フレームは安定しないのである程度まわす
/// </summary>
/// <returns>IEnumerator</returns>
IEnumerator Start()
{
for (int index = 0; index < 5; index++)
{
Setting();
yield return 0;
}
}
#if UNITY_EDITOR
/// <summary>
/// Editor用
/// </summary>
void LateUpdate()
{
TargetCanvas = GetComponentInParent<Canvas>();
TargetCanvasRect = TargetCanvas.GetComponent<RectTransform>();
TargetCanvasScaler = GetComponentInParent<CanvasScaler>();
if (!UnityEditor.EditorApplication.isPlaying)
Setting();
}
#endif
/// <summary>
/// 設定
/// </summary>
void Setting()
{
if (!targetArea)
return;
TargetCanvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
TargetCanvasScaler.referenceResolution = referenceResolution;
TargetCanvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
var tempRect = GetSafeRectOffset(true);
var tempRectOffset = GetSafeRectOffset2(true);
var tempOffsetArea = GetOffsetArea();
var positionX = tempRectOffset.left - tempRectOffset.right;
var positionY = tempRectOffset.top - tempRectOffset.bottom;
var scale = GetSafeScale(true);
// ストレッチ
var tempVector = referenceResolution;
if (stretchWidth)
tempVector.x = Mathf.Max(tempVector.x, (TargetCanvasRect.sizeDelta.x - tempRect.Horizontal) / scale);
if (stretchHeight)
tempVector.y = Mathf.Max(tempVector.y, (TargetCanvasRect.sizeDelta.y - tempRect.Vertical) / scale);
// 配置
targetArea.sizeDelta = tempVector;
var tempPivot = new Vector2(0.5f, 0.5f);
targetArea.localScale = new Vector3(scale, scale, 1);
{
var tempOffsetArea2 = tempOffsetArea;
if (safeHalfOffset)
{
tempOffsetArea2.left *= 0.5f;
tempOffsetArea2.right *= 0.5f;
tempOffsetArea2.top *= 0.5f;
tempOffsetArea2.bottom *= 0.5f;
}
if (anchor == TextAnchor.MiddleCenter)
{
positionX += Mathf.Clamp(safeOffset.x, -tempOffsetArea2.left, tempOffsetArea2.right);
positionY += Mathf.Clamp(-safeOffset.y, -tempOffsetArea2.top, tempOffsetArea2.bottom);
}
switch (anchor)
{
case TextAnchor.LowerCenter:
case TextAnchor.LowerLeft:
case TextAnchor.LowerRight:
tempPivot.y = 0;
positionY = -tempRect.bottom;
positionY += Mathf.Clamp(-safeOffset.y, -(tempOffsetArea2.top + tempOffsetArea2.bottom), 0);
break;
case TextAnchor.UpperCenter:
case TextAnchor.UpperLeft:
case TextAnchor.UpperRight:
tempPivot.y = 1;
positionY = tempRect.top;
positionY += Mathf.Clamp(-safeOffset.y, 0, tempOffsetArea2.top + tempOffsetArea2.bottom);
break;
}
switch (anchor)
{
case TextAnchor.LowerLeft:
case TextAnchor.MiddleLeft:
case TextAnchor.UpperLeft:
tempPivot.x = 0;
positionX = tempRect.left;
positionX += Mathf.Clamp(safeOffset.x, 0, tempOffsetArea2.left + tempOffsetArea2.right);
break;
case TextAnchor.LowerRight:
case TextAnchor.MiddleRight:
case TextAnchor.UpperRight:
tempPivot.x = 1;
positionX = -tempRect.right;
positionX += Mathf.Clamp(safeOffset.x, -(tempOffsetArea2.left + tempOffsetArea2.right), 0);
break;
}
}
targetArea.pivot = tempPivot;
targetArea.anchorMin = tempPivot;
targetArea.anchorMax = tempPivot;
targetArea.anchoredPosition3D = new Vector3(positionX, -positionY, 0);
}
/// <summary>
/// 範囲の取得
/// </summary>
/// <param name="useSafe">useSafe</param>
/// <returns>範囲</returns>
public SafeAreaHelper.RectFloatOffset GetSafeRectOffset(bool addDeviceSafeArea)
{
var result = new SafeAreaHelper.RectFloatOffset(0, 0, 0, 0);
if (safeRect != null)
{
result.left += safeRect.left;
result.right += safeRect.right;
result.top += safeRect.top;
result.bottom += safeRect.bottom;
}
if (addDeviceSafeArea)
{
var addRect = AddDeviceSafeArea();
result.left += addRect.left;
result.right += addRect.right;
result.top += addRect.top;
result.bottom += addRect.bottom;
}
return result;
}
/// <summary>
/// SafeScaleの取得
/// </summary>
/// <returns>値</returns>
public float GetSafeScale(bool addDeviceSafeArea)
{
var tempRect = GetSafeRectOffset(addDeviceSafeArea);
var scaleX = (TargetCanvasRect.sizeDelta.x - tempRect.Horizontal) / referenceResolution.x;
var scaleY = (TargetCanvasRect.sizeDelta.y - tempRect.Vertical) / referenceResolution.y;
var scale = Mathf.Min(scaleX, scaleY);
scale = Mathf.Min(1, scale);
return scale;
}
/// <summary>
/// 範囲の取得
/// </summary>
/// <param name="useSafe">useSafe</param>
/// <returns>範囲</returns>
public SafeAreaHelper.RectFloatOffset GetSafeRectOffset2(bool addDeviceSafeArea)
{
var tempRect = GetSafeRectOffset(addDeviceSafeArea);
var offsetMinX = Mathf.Max(0, tempRect.left - tempRect.Horizontal * 0.5f);
var offsetMinY = Mathf.Max(0, tempRect.top - tempRect.Vertical * 0.5f);
var offsetMaxX = Mathf.Max(0, tempRect.right - tempRect.Horizontal * 0.5f);
var offsetMaxY = Mathf.Max(0, tempRect.bottom - tempRect.Vertical * 0.5f);
return new SafeAreaHelper.RectFloatOffset(offsetMinX, offsetMaxX, offsetMinY, offsetMaxY);
}
/// <summary>
/// デバイスセーフエリアの追加分を取得する
/// </summary>
/// <returns>エリア</returns>
SafeAreaHelper.RectFloatOffset AddDeviceSafeArea()
{
var scaleX = TargetCanvasRect.sizeDelta.x / Screen.width;
var scaleY = TargetCanvasRect.sizeDelta.y / Screen.height;
var result = new SafeAreaHelper.RectFloatOffset()
{
left = (int)(Screen.safeArea.xMin * scaleX),
right = (int)((Screen.width - Screen.safeArea.xMax) * scaleX),
top = (int)((Screen.height - Screen.safeArea.yMax) * scaleY),
bottom = (int)(Screen.safeArea.yMin * scaleY),
};
Vector2 sizeDelta = Vector2.zero;
GetOutRect(ref sizeDelta, SafeAreaHelper.Anchor.Left);
result.left = Mathf.Max(0, result.left - sizeDelta.x);
GetOutRect(ref sizeDelta, SafeAreaHelper.Anchor.Right);
result.right = Mathf.Max(0, result.right - sizeDelta.x);
GetOutRect(ref sizeDelta, SafeAreaHelper.Anchor.Top);
result.top = Mathf.Max(0, result.top - sizeDelta.y);
GetOutRect(ref sizeDelta, SafeAreaHelper.Anchor.Bottom);
result.bottom = Mathf.Max(0, result.bottom - sizeDelta.y);
return result;
}
/// <summary>
/// 余白を取得する
/// </summary>
/// <returns>余白</returns>
public SafeAreaHelper.RectFloatOffset GetOffsetArea()
{
var scaleX = TargetCanvasRect.sizeDelta.x / Screen.width;
var scaleY = TargetCanvasRect.sizeDelta.y / Screen.height;
var deviceAreaRect = new SafeAreaHelper.RectFloatOffset()
{
left = (int)(Screen.safeArea.xMin * scaleX),
right = (int)((Screen.width - Screen.safeArea.xMax) * scaleX),
top = (int)((Screen.height - Screen.safeArea.yMax) * scaleY),
bottom = (int)(Screen.safeArea.yMin * scaleY),
};
var result = new SafeAreaHelper.RectFloatOffset();
Vector2 sizeDelta = Vector2.zero;
GetOutRect(ref sizeDelta, SafeAreaHelper.Anchor.Left);
result.left = Mathf.Max(0, sizeDelta.x - deviceAreaRect.left - safeRect.left);
GetOutRect(ref sizeDelta, SafeAreaHelper.Anchor.Right);
result.right = Mathf.Max(0, sizeDelta.x - deviceAreaRect.right - safeRect.right);
GetOutRect(ref sizeDelta, SafeAreaHelper.Anchor.Top);
result.top = Mathf.Max(0, sizeDelta.y - deviceAreaRect.top - safeRect.top);
GetOutRect(ref sizeDelta, SafeAreaHelper.Anchor.Bottom);
result.bottom = Mathf.Max(0, sizeDelta.y - deviceAreaRect.bottom - safeRect.bottom);
return result;
}
/// <summary>
/// 範囲外の取得
/// </summary>
/// <param name="sizeDelta">出力されるサイズ</param>
/// <param name="targetAnchor">anchor</param>
/// <param name="targetUseSafe">useSafe</param>
/// <param name="targetUseDeviceSafe">useDeviceSafe</param>
/// <param name="targetInSafeRect">inSafeRect</param>
/// <param name="safeArea">safeArea</param>
/// <param name="addDeviceSafeArea">addDeviceSafeArea</param>
public void GetOutRect(ref Vector2 sizeDelta, SafeAreaHelper.Anchor targetAnchor)
{
var tempRectOffset = GetSafeRectOffset2(false);
var positionX = tempRectOffset.left - tempRectOffset.right;
var positionY = tempRectOffset.top - tempRectOffset.bottom;
var scale = GetSafeScale(false);
sizeDelta.x = 0;
sizeDelta.y = 0;
switch (targetAnchor)
{
case SafeAreaHelper.Anchor.Left:
sizeDelta.x = TargetCanvasRect.sizeDelta.x * 0.5f + positionX - referenceResolution.x * scale * 0.5f;
sizeDelta.y = TargetCanvasRect.sizeDelta.y;
break;
case SafeAreaHelper.Anchor.Right:
sizeDelta.x = TargetCanvasRect.sizeDelta.x * 0.5f - positionX - referenceResolution.x * scale * 0.5f;
sizeDelta.y = TargetCanvasRect.sizeDelta.y;
break;
case SafeAreaHelper.Anchor.Top:
sizeDelta.y = TargetCanvasRect.sizeDelta.y * 0.5f + positionY - referenceResolution.y * scale * 0.5f;
sizeDelta.x = TargetCanvasRect.sizeDelta.x;
break;
case SafeAreaHelper.Anchor.Bottom:
sizeDelta.y = TargetCanvasRect.sizeDelta.y * 0.5f - positionY - referenceResolution.y * scale * 0.5f;
sizeDelta.x = TargetCanvasRect.sizeDelta.x;
break;
}
}
}
}


9:21とか、無茶ぶりの多い解像度が増えてきたのでUIの設計も大変にですね。
次回は、余った領域に黒帯を表示する、のことです。






0 件のコメント :

コメントを投稿