效果
效果如图所示
蓝色方块跟随红色方块左上角,当蓝色方块左边碰撞到屏幕的时候,其会变成在右边,然后回去到一定距离后又变成在左边。
上下则是出界的时候,蓝色方块不移动,等红色方块下移,并且蓝色方块与红色方块的垂直距离大于一定的时候,蓝色方块才跟随移动。
上方的界限是屏幕顶部,下方是某个自定义的距离。这些都可以根据情况修改参数
思路
这个的思路比一的难了一些,主要是之前没做过类似的,加上状态不好,一开始想法比较混乱。
做了之后看着代码分析就感觉容易了
具体思路是,
每次位移时计算出,新的位移点下,蓝色方块的顶部是否有超出屏幕顶部,底部是否有低于给定的底部。
如果没有则垂直方向上进行新的位移计算并重新赋值给蓝块。
这样的逻辑其实还缺了一部分,测试发现这样的逻辑会导致个问题
鼠标在屏幕上下边缘点击来更改等突然大距离的更改红块而导致的蓝块的竖直方向不改变。
所以在判断上下超界时,还要重新给蓝块赋值个上下的的边界位置值。
代码
参数部分代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 提取预览UI处理回弹问题的公共部分
/// </summary>
public partial class VerAOutBoundUI
{
[Header("预览窗口位置偏移")]
//这些偏移只适用于在3840*2160的机器上 其他分辨率的机器如1920*1080的机器要做对应的比例换算
[SerializeField] private float topPosYDelta = 300;
[SerializeField] private float bottomPosYDelta = -300;
[SerializeField] private float leftPosXDelta = -570;
[SerializeField] private float rightPosXDelta = 570;
private Vector2 topLeftPosOffset;
private Vector2 topRightPosOffset;
private Vector2 bottomRightPosOffset;
private Vector2 bottomLeftPosOffset;
[SerializeField] private Canvas _rootTransformCanvas;
public Canvas RootTransformCanvas => _rootTransformCanvas;
void InitPosOffset()
{
ResetOffsetPosByResolution();
topLeftPosOffset = new Vector2(leftPosXDelta, topPosYDelta);
topRightPosOffset = new Vector2(rightPosXDelta, topPosYDelta);
bottomRightPosOffset = new Vector2(rightPosXDelta, bottomPosYDelta);
bottomLeftPosOffset = new Vector2(leftPosXDelta, bottomPosYDelta);
}
/// <summary>
/// 根据屏幕分辨率重新调整偏移值
/// 因为默认的偏移只适用于在3840*2160的机器上
/// 其他分辨率的机器如1920*1080的机器要做对应的比例换算
/// </summary>
void ResetOffsetPosByResolution()
{
#if UNITY_EDITOR
float ratioWidth = 0;
float ratioHeight = 0;
if (RootTransformCanvas != null )
{
int canvasDisplay = RootTransformCanvas.targetDisplay;
if (Display.displays[canvasDisplay] != null)
{
float editorWidth = Display.displays[canvasDisplay].renderingWidth;
float editorHeight = Display.displays[canvasDisplay].renderingHeight;
ratioWidth = (editorWidth * 1f) / (Const.standardResolutionWidth * 1f);
ratioHeight = (editorHeight * 1f) / (Const.standardResolutionHeight * 1f);
}
}
#else
float ratioWidth = (Screen.currentResolution.width * 1f) / (Const.standardResolutionWidth * 1f);
float ratioHeight = (Screen.currentResolution.height * 1f) / (Const.standardResolutionHeight * 1f);
#endif
leftPosXDelta *= ratioWidth;
topPosYDelta *= ratioHeight;
rightPosXDelta *= ratioWidth;
bottomPosYDelta *= ratioHeight;
}
}
左边回弹部分
using UnityEngine;
/// <summary>
/// 在Marker距离屏幕右边一定距离往回走一定距离以后
/// 将预览画面自动往右靠 防止遮挡
/// 与VerA处理边界出境的方式联合使用
/// 即与RoamingPreviewCameraUIOutBoundBouncePart一起配合使用
/// </summary>
public partial class VerAOutBoundUI
{
void LeftAdjustPos()
{
if (currentHorizontalPosFlag == Const.HorizontalPosFlag.Right)
{
currentHorizontalPosFlag = Const.HorizontalPosFlag.Left;
ResetOffsetPos();
}
}
void JudgeLeftMoveCorrectByBounce()
{
if (currentHorizontalPosFlag == Const.HorizontalPosFlag.Left)
{
return;
}
if (targetPosTransform && followerRectTransform)
{
#if !UNITY_EDITOR
float horScreenBoundPos = Screen.currentResolution.width;
#else
float horScreenBoundPos = 0;
if (RootTransformCanvas)
{
int canvasDisplay = RootTransformCanvas.targetDisplay;
if (Display.displays[canvasDisplay] != null)
{
horScreenBoundPos = Display.displays[canvasDisplay].renderingWidth;
}
}
#endif
//这里要特别注意 传入的是MarkerPosTransform.position 而不是 HangerRectTransform.position
Vector3 worldPos = targetPosTransform.position;
Vector3 currentSimulateLeftPos = new Vector3(worldPos.x + leftPosXDelta,
worldPos.y, worldPos.z);
// float halfWidth = RenderUIImageRectTransform.rect.width / 2f;
//屏幕左边边缘的x坐标是0
float screenLeftPosX = 0;
float toAdjustScreenHorizontalPos = screenLeftPosX + halfWidth;
// print(" currentSimulateRightPos " + currentSimulateLeftPos +
// " halfWidth " + halfWidth +
// " screenBoundPos " + horScreenBoundPos +
// " toAdjustScreenPos " + toAdjustScreenHorizontalPos +
// " worldPos " + worldPos
// + " currentSimulateRightPos.x < toAdjustScreenHorizontalPos " +
// (currentSimulateLeftPos.x < toAdjustScreenHorizontalPos));
if (currentSimulateLeftPos.x > toAdjustScreenHorizontalPos)
{
LeftAdjustPos();
}
}
}
}
主体
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 处理关于边界出境的问题
/// 上下用贴边对齐和超距尾随的方式进行
/// 左右用回弹的方式进行
/// 这里对这种处理方式成为VerA
/// </summary>
public partial class VerAOutBoundUI : MonoBehaviour
{
private float halfHeight;
private float halfWidth;
[SerializeField]
private float minPosThreshold = 1000;
[SerializeField]
private RectTransform followerRectTransform;
[SerializeField]
private RectTransform rootCanvasRectTransform;
[SerializeField]
private RectTransform targetPosTransform;
private float topPosYMinusHalfHeight;
private float bottomPosYPlusHalfHeight;
private Const.HorizontalPosFlag currentHorizontalPosFlag;
private Const.VerticalPosFlag currentVerticalPosFlag;
/// <summary>
/// 跟随物体相对被跟随物体的位置偏移
/// </summary>
private Vector2 currentPosOffset;
private void Awake()
{
InitOutBoundParam();
InitPosOffset();
}
void InitOutBoundParam()
{
float heightRatio = Utils.GetResolutionHeightRatio(rootCanvasRectTransform);
float widthRatio = Utils.GetResolutionWidthRatio(rootCanvasRectTransform);
//这里乘以 heightRatio 是因为出现了一种情况
// RenderUIImageRectTransform.rect.height和width
// 在这里不管分辨率是2K还是4K都出现了数值一致的情况
halfHeight = followerRectTransform.rect.height / 2f * heightRatio;
halfWidth = followerRectTransform.rect.width / 2f * widthRatio;
int displau1Index = 0;
var maxHeight = Utils.GetGameViewTopBoundYPos(displau1Index);
currentVerticalPosFlag = Const.VerticalPosFlag.Top;
currentHorizontalPosFlag = Const.HorizontalPosFlag.Right;
//这里乘以 heightRatio 是因为minPosThreshold本身是个固定值 本身只有4K才适用
//在2K的时候要做相应的缩小
minPosThreshold *= heightRatio;
topPosYMinusHalfHeight = maxHeight - halfHeight;
bottomPosYPlusHalfHeight = minPosThreshold + halfHeight;
}
private void Update()
{
RefreshPosJudgingOutBounds();
}
#region 垂直判断和操作部分
/// <summary>
/// 判断位置的y值是否比屏幕顶部的位置坐标大
/// 即超过屏幕顶部
/// </summary>
/// <param name="yPos"></param>
/// <returns></returns>
bool JudgeYPosVerticalOverflow(float yPos)
{
int displau1Index = 0;
var maxHeight = Utils.GetGameViewTopBoundYPos(displau1Index);
if (yPos < maxHeight)
{
return false;
}
// Debug.Log("JudgeYPosVerticalOverflow yPos " + yPos + " maxHeight " + maxHeight);
return true;
}
/// <summary>
/// 判断位置的y值是否比要求的最小底部位置小
/// 即超过屏幕底部
/// </summary>
/// <param name="yPos"></param>
/// <returns></returns>
bool JudgeYPosVerticalSink(float yPos)
{
if (yPos > minPosThreshold)
{
return false;
}
// Debug.Log("JudgeYPosVerticalSink yPos " + yPos + " minPosThreshold " + minPosThreshold);
return true;
}
#endregion
#region 水平判断和操作部分
/// <summary>
/// 判断相机画布是否水平方向超出屏幕
/// 以及超出屏幕后设置标志位
/// 要求预览图的pivot在中心
/// </summary>
void JudgeImageHorizontalOutOfScreen()
{
Vector3 worldPos = followerRectTransform.position;
float halfWidth = followerRectTransform.rect.width / 2f;
float rightPlaceXPos = worldPos.x + halfWidth;
float leftPlaceXPos = worldPos.x - halfWidth;
// Debug.Log(" topPlaceHeight " + topPlaceHeight);
// Debug.Log(" halfWidth " + halfWidth);
int displau1Index = 0;
var maxWidth = Utils.GetGameViewRightBoundXPos(displau1Index);
if (rightPlaceXPos > maxWidth)
{
currentHorizontalPosFlag = Const.HorizontalPosFlag.Left;
}
else if (leftPlaceXPos < 0)
{
currentHorizontalPosFlag = Const.HorizontalPosFlag.Right;
}
}
#endregion
void ResetOffsetPos()
{
switch (currentVerticalPosFlag)
{
case Const.VerticalPosFlag.Top:
switch (currentHorizontalPosFlag)
{
case Const.HorizontalPosFlag.Left:
currentPosOffset = topLeftPosOffset;
break;
case Const.HorizontalPosFlag.Right:
currentPosOffset = topRightPosOffset;
break;
}
break;
case Const.VerticalPosFlag.Bottom:
switch (currentHorizontalPosFlag)
{
case Const.HorizontalPosFlag.Left:
currentPosOffset = bottomLeftPosOffset;
break;
case Const.HorizontalPosFlag.Right:
currentPosOffset = bottomRightPosOffset;
break;
}
break;
}
}
private void RefreshPosJudgingOutBounds()
{
// print(" RefreshPosJudgingOutBounds not overflow ");
HorizontalAdjust();
VerticalAdjust();
}
private void VerticalAdjust()
{
//x值是一直都要更新的
followerRectTransform.position = new Vector3(targetPosTransform.position.x + currentPosOffset.x,
followerRectTransform.position.y,
followerRectTransform.position.z);
float topPosNew = targetPosTransform.position.y + currentPosOffset.y + halfHeight;
float bottomPosNew = targetPosTransform.position.y + currentPosOffset.y - halfHeight;
// Debug.Log( " MarkerPosTransform.position.y " + MarkerPosTransform.position.y +
// " currentPosOffset.y " + currentPosOffset.y +
// " halfHeight " + halfHeight);
//这里更新y值
//不做判断的话,会出现出界时候与上面两个判断隔一帧互相执行一次的情况
//即上面判断出界了矫正,然后矫正后不走上面的判断走下面的,然后又出界,如此往复
if (!JudgeYPosVerticalOverflow(topPosNew) &&
!JudgeYPosVerticalSink(bottomPosNew))
{
followerRectTransform.position = new Vector3(followerRectTransform.position.x,
targetPosTransform.position.y + currentPosOffset.y,
followerRectTransform.position.z);
}
else if (JudgeYPosVerticalOverflow(topPosNew))
{
// Debug.LogError(" ------------------- JudgeYPosVerticalOverflow ");
followerRectTransform.position = new Vector3(
followerRectTransform.position.x,
topPosYMinusHalfHeight,
followerRectTransform.position.z);
}
else if (JudgeYPosVerticalSink(bottomPosNew))
{
// Debug.LogError(" ------------------- JudgeYPosVerticalSink ");
followerRectTransform.position = new Vector3(followerRectTransform.position.x,
bottomPosYPlusHalfHeight,
followerRectTransform.position.z);
}
//在屏幕边境的时候 不能用currentPosOffset.y = maxHeight - halfHeight - MarkerUIPos.y
//来更新currentPosOffset.y,然后再用currentPosOffset + MarkerPosTransform.position来更新HangerRectTransform.position
//因为这个更新代码不管放在unity的哪种类型update方法都会造成预览窗口抖动
// HangerRectTransform.position = MarkerPosTransform.position + new Vector3(
// currentPosOffset.x,
// currentPosOffset.y,
// 0);
}
private void HorizontalAdjust()
{
JudgeImageHorizontalOutOfScreen();
ResetOffsetPos();
JudgeLeftMoveCorrectByBounce();
}
}
工程
工程链接