提示:素材來源網路,侵權必刪

UI翻書效果
- 效果圖
- 一、準備作業
- 1.新建Unity工程
- 2.新建Hierarchy面板
- 二、使用步驟
- 1.新建UIBook腳本,掛在UIBook上
- 2.新建BookModels腳本
- 3.新建DragButton腳本
- 4.新建DragLeftPage腳本
- 5.新建DragRightPage腳本
- 6.新建IDragPage腳本
- 7.新建Page腳本
- 8.新建Shadow腳本
- 9.新建TheDraggingPage腳本
- 3.總結
效果圖

提示:以下是本篇文章正文內容,下面案例可供參考
一、準備作業
1.新建Unity工程
準備幾張圖片,新建一個檔案夾放進去,然后把檔案夾放在Resources檔案夾下
2.新建Hierarchy面板
如圖

二、使用步驟
1.新建UIBook腳本,掛在UIBook上
代碼如下(示例):
using System;
using System.Collections;
using UnityEngine;
namespace HKZ
{
public class UIBook : MonoBehaviour
{
//組件
private RectTransform rect;
private Sprite[] bookSprites;//存放所以的圖片(書頁)
private RectTransform clippingMask;
//參考
/// <summary>
/// 正在翻頁的書頁的左面
/// </summary>
private TheDraggingPage leftSideOfPage;
/// <summary>
/// 正在翻頁的書頁的右面
/// </summary>
private TheDraggingPage rightSideOfPage;
/// <summary>
/// 左側靜止頁面
/// </summary>
private Page leftPage;
/// <summary>
/// 右側靜止頁面
/// </summary>
private Page rightPage;
private DragPageBase dragPage;
/// <summary>
/// 資料類
/// </summary>
private BookModels bookModels;
//資料
private int currentLeftId;//當前左邊頁數
private float aniDuration = 0.5f;
private bool isDragging;
public int CurrentLeftId
{
get
{
return currentLeftId;
}
set
{
currentLeftId = value;
if (currentLeftId < -1)
{
currentLeftId = -1;
}
else if (currentLeftId > bookSprites.Length - 1)
{
currentLeftId = bookSprites.Length - 1;
}
}
}
private void Start()
{
Canvas canvas;
InitAddComponent(out canvas);//初始化
InitDate(canvas);
UpdateID(isDragging);
}
/// <summary>
/// 初始化,找到子項并添加相應的腳本(正在翻頁,靜態頁面)
/// </summary>
private void InitAddComponent(out Canvas canvas)
{
rect = GetComponent<RectTransform>();//獲取組件
canvas = null;
foreach (Canvas c in rect.GetComponentsInParent<Canvas>())
{
if (c.isRootCanvas)
{
canvas = c;
break;
}
}
clippingMask = rect.Find("ClippingMask").GetComponent<RectTransform>();
rightPage = rect.Find("RightPage").gameObject.AddComponent<Page>();
leftPage = rect.Find("LeftPage").gameObject.AddComponent<Page>();
rightSideOfPage = rect.Find("RightSide").gameObject.AddComponent<TheDraggingPage>();
leftSideOfPage = rect.Find("LeftSide").gameObject.AddComponent<TheDraggingPage>();
rect.Find("RightDragButton").gameObject.AddComponent<DragButton>().
Init(OnMouseDragRightPage, OnUpdatePage, OnEndDragRightPage);
rect.Find("LeftDragButton").gameObject
.AddComponent<DragButton>()
.Init(OnMouseDragLeftPage, OnUpdatePage, OnEndDragLeftPage);
rightPage.Init(GetSprite);
leftPage.Init(GetSprite);
rightSideOfPage.Init(GetSprite);
leftSideOfPage.Init(GetSprite);
}
private Sprite GetSprite(int index)
{
if (index>=0&&index<bookSprites.Length)
{
return bookSprites[index];
}
return null;
}
/// <summary>
/// 初始化資料(加載圖片,設定整個UIBook大小)
/// </summary>
private void InitDate(Canvas canvas)
{
bookModels = new BookModels();//創建并初始化
bookSprites = Resources.LoadAll<Sprite>("Book");//加載所有的圖片(書頁)
if (bookSprites.Length > 0)
{
rect.sizeDelta = new Vector2(bookSprites[0].rect.width * 2, bookSprites[0].rect.height);//設定UIBook大小
}
CurrentLeftId = -1;
isDragging = false;
float scaleFactor = 1;//默認圖片縮放比例為1
if (canvas != null)
{
scaleFactor = canvas.scaleFactor;
}
//計算螢屏上書頁的顯示尺寸
float pageWidth = rect.rect.width * scaleFactor / 2; ;//書頁寬度
float pageHeight = rect.rect.height * scaleFactor;//書頁高度
//底邊中心點的獲取
Vector3 pos = rect.position + Vector3.down * pageHeight / 2;
bookModels.BottomCenter = World2LocalPos(pos);
//右下角坐標
pos = rect.position + Vector3.down * pageHeight / 2 + Vector3.right * pageWidth;
bookModels.RightCorner = World2LocalPos(pos);
//上邊中心點的獲取
pos = rect.position + Vector3.up * pageHeight / 2;
bookModels.TopCenter = World2LocalPos(pos);
//左下角坐標的獲取
pos = rect.position + Vector3.down * pageHeight / 2 + Vector3.left * pageWidth;
bookModels.LeftCorner = World2LocalPos(pos);
float width = rect.rect.width / 2;
float height = rect.rect.height;
//每頁的寬度
bookModels.PageWidth = width;
//對角線的計算
bookModels.PageDiagonal = Mathf.Sqrt(Mathf.Pow(width, 2) + Mathf.Pow(height, 2));
//設定ClippingMask的大小
clippingMask.sizeDelta = new Vector2(bookModels.PageDiagonal, bookModels.PageDiagonal + bookModels.PageWidth);
//反轉切面的軸心點Y值
bookModels.ClippingPivotY = bookModels.PageWidth / clippingMask.sizeDelta.y;
leftSideOfPage.InitShadow(new Vector2(bookModels.PageDiagonal, bookModels.PageDiagonal));
rightSideOfPage.InitShadow(new Vector2(bookModels.PageDiagonal, bookModels.PageDiagonal));
}
/// <summary>
/// 獲取clippingMask
/// </summary>
/// <returns></returns>
public RectTransform GetClippingMask()
{
return clippingMask;
}
/// <summary>
/// 世界坐標轉換為local坐標
/// </summary>
/// <param name="world"></param>
/// <returns></returns>
private Vector3 World2LocalPos(Vector3 world)
{
return rect.InverseTransformPoint(world);
}
/// <summary>
/// Local坐標轉化世界坐標
/// </summary>
/// <param name="local"></param>
/// <returns></returns>
public Vector3 Local2WorldPos(Vector3 local)
{
return rect.TransformPoint(local);
}
private void UpdateID(bool isDragging)
{
if (isDragging)
{
leftPage.ID = CurrentLeftId;
leftSideOfPage.ID = CurrentLeftId + 1;
rightSideOfPage.ID = CurrentLeftId + 2;
rightPage.ID = CurrentLeftId + 3;
}
else
{
leftPage.ID = CurrentLeftId;
rightPage.ID = CurrentLeftId + 1;
}
}
//開始拖動
private void OnMouseDragRightPage()
{
if (CurrentLeftId<bookSprites.Length-1)
{
isDragging = true;
dragPage = new DragRightPage(this, bookModels, leftSideOfPage, rightSideOfPage, rightPage.transform.position);
dragPage.BeginDragPage(World2LocalPos(Input.mousePosition));
UpdateID(isDragging);
}
}
private void OnMouseDragLeftPage()
{
if (CurrentLeftId > 0)
{
dragPage = new DragLeftPage(this, bookModels, rightSideOfPage, leftSideOfPage, leftPage.transform.position);
dragPage.BeginDragPage(World2LocalPos(Input.mousePosition));
isDragging = true;
CurrentLeftId -= 2;
UpdateID(isDragging);
}
}
//持續拖動
private void OnUpdatePage()
{
if (isDragging)
{
dragPage.UpdateDrag();
}
}
//結束拖動
private void OnEndDragRightPage()
{
if (isDragging)
{
isDragging = false;
bool isLeft = JudgeCornerIsLeft();
dragPage.EndDragPage(() =>AniEnd(isLeft));
}
}
//結束拖動
private void OnEndDragLeftPage()
{
if (isDragging)
{
isDragging = false;
bool isLeft = JudgeCornerIsLeft();
dragPage.EndDragPage(() => AniEnd(isLeft));
}
}
private bool JudgeCornerIsLeft()
{
return bookModels.CurrentPageCorner.x < bookModels.BottomCenter.x;
}
private void AniEnd(bool isLeft)
{
if (isLeft)
{
CurrentLeftId += 2;
}
}
/// <summary>
/// 獲取點擊位置
/// </summary>
/// <returns></returns>
public Vector3 GetClickPos()
{
if (isDragging)
{
return World2LocalPos(Input.mousePosition);
}
else
{
return bookModels.ClickPoint;
}
}
/// <summary>
/// 計算拖拽角的位置
/// </summary>
/// <param name="click"></param>
public Vector3 CulculateDraggingCorner(Vector3 click)
{
Vector3 corner = Vector3.zero;
corner = LimitBotomCenter(click);
return LimitTopCenter(corner);
}
/// <summary>
/// 獲取底邊中心點
/// </summary>
/// <param name="click"></param>
/// <returns></returns>
private Vector3 LimitBotomCenter(Vector3 click)
{
Vector3 offset = click - bookModels.BottomCenter;
float radians = Mathf.Atan2(offset.y, offset.x);
Vector3 cornerLimit = new Vector3(
bookModels.PageWidth * Mathf.Cos(radians)
, bookModels.PageWidth * Mathf.Sin(radians))
+ bookModels.BottomCenter;
float distance = Vector2.Distance(click, bookModels.BottomCenter);
if (distance < bookModels.PageWidth)
{
return click;
}
else
{
return cornerLimit;
}
}
/// <summary>
/// 獲取上邊中心點
/// </summary>
/// <param name="click"></param>
/// <returns></returns>
private Vector3 LimitTopCenter(Vector3 corner)
{
Vector3 offset = corner - bookModels.TopCenter;
float radians = Mathf.Atan2(offset.y, offset.x);
Vector3 cornerLimit = new Vector3(
bookModels.PageDiagonal * Mathf.Cos(radians)
, bookModels.PageDiagonal * Mathf.Sin(radians))
+ bookModels.TopCenter;
float distance = Vector2.Distance(corner, bookModels.TopCenter);
if (distance > bookModels.PageDiagonal)
return cornerLimit;
return corner;
}
/// <summary>
/// 計算折疊線的角度數
/// </summary>
/// <param name="corner"></param>
/// <param name="bookCorner"></param>
/// <returns></returns>
public float CulculateFoldAngle(Vector3 corner, Vector3 bookCorner, out Vector3 bottomCrossPoint)
{
Vector3 twoCornerCenter = (corner + bookCorner) / 2;
Vector3 offset = bookCorner - twoCornerCenter;
float randians = Mathf.Atan2(offset.y, offset.x);
float offsetX = twoCornerCenter.x - offset.y * Mathf.Tan(randians);
offsetX = LimitOffsetX(offsetX, bookCorner, bookModels.BottomCenter);
bottomCrossPoint = new Vector3(offsetX, bookModels.BottomCenter.y);
offset = bottomCrossPoint - twoCornerCenter;
return Mathf.Atan(offset.y / offset.x) * Mathf.Rad2Deg; //randians to degress
}
private float LimitOffsetX(float offsetX, Vector3 bookCorner, Vector3 bottomCenter)
{
if (offsetX > bottomCenter.x && bottomCenter.x > bookCorner.x)
return bottomCenter.x;
if (offsetX < bottomCenter.x && bottomCenter.x < bookCorner.x)
return bottomCenter.x;
return offsetX;
}
public void FlipAni(Vector3 target, Action onComplete)
{
StartCoroutine(PageAni(target, aniDuration, ()=>
{
if (onComplete!=null)
{
onComplete();
}
ResetDate();
}));
}
private void ResetDate()
{
UpdateID(isDragging);
leftSideOfPage.SetActiveState(false);
rightSideOfPage.SetActiveState(false);
}
private IEnumerator PageAni(Vector3 target,float duration,Action onComplete)
{
Vector3 offset = (target - bookModels.ClickPoint) / duration;
float symbol = (target - bookModels.ClickPoint).x;
yield return new WaitUntil(() =>
{
bookModels.ClickPoint += offset * Time.deltaTime;
dragPage.UpdateDrag();
if (symbol>0)
{
return bookModels.ClickPoint.x >= target.x;
}
else
{
return bookModels.ClickPoint.x <= target.x;
}
});
if (onComplete!=null)
{
onComplete();
}
}
}
}
2.新建BookModels腳本
代碼如下(示例):
using UnityEngine;
namespace HKZ
{
public class BookModels
{
/// <summary>
/// 一頁的寬度
/// </summary>
public float PageWidth;
/// <summary>
/// 對角線
/// </summary>
public float PageDiagonal;
/// <summary>
/// 底邊中心點
/// </summary>
public Vector3 BottomCenter;
/// <summary>
/// 頂點中心點
/// </summary>
public Vector3 TopCenter;
/// <summary>
/// 當前被拖動的書頁頂點位置
/// </summary>
public Vector3 CurrentPageCorner;
/// <summary>
/// 右邊書頁右下角頂點
/// </summary>
public Vector3 RightCorner;
/// <summary>
/// 左邊書頁右下角頂點
/// </summary>
public Vector3 LeftCorner;
/// <summary>
/// 當前點擊的點
/// </summary>
public Vector3 ClickPoint;
/// <summary>
/// 反轉切面的軸心點Y值
/// </summary>
public float ClippingPivotY;
}
}
3.新建DragButton腳本
代碼如下(示例):
using System;
using UnityEngine;
using UnityEngine.EventSystems;
namespace HKZ
{
public class DragButton : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
private Action onBeginDrag;
private Action onDrag;
private Action onEndDrag;
/// <summary>
/// 初始化資料
/// </summary>
/// <param name="onBeginDrag"></param>
/// <param name="onDrag"></param>
/// <param name="onEndDrag"></param>
public void Init(Action onBeginDrag, Action onDrag, Action onEndDrag)
{
this.onBeginDrag = onBeginDrag;
this.onDrag = onDrag;
this.onEndDrag = onEndDrag;
}
/// <summary>
/// 開始拖動
/// </summary>
/// <param name="eventData"></param>
public void OnBeginDrag(PointerEventData eventData)
{
if (onBeginDrag!=null)
{
onBeginDrag();
}
}
/// <summary>
/// 持續拖動
/// </summary>
/// <param name="eventData"></param>
public void OnDrag(PointerEventData eventData)
{
if (onDrag != null)
{
onDrag();
}
}
/// <summary>
/// 結束拖動
/// </summary>
/// <param name="eventData"></param>
public void OnEndDrag(PointerEventData eventData)
{
if (onEndDrag!=null)
{
onEndDrag();
}
}
}
}
4.新建DragLeftPage腳本
代碼如下(示例):
using UnityEngine;
namespace HKZ
{
public class DragLeftPage : DragPageBase
{
public DragLeftPage(UIBook uIBook, BookModels bookModels, TheDraggingPage frontPage, TheDraggingPage backPage, Vector3 startPos)
: base(uIBook, bookModels, frontPage, backPage, startPos)
{
}
protected override Vector3 GetBookCorner()
{
return bookModels.LeftCorner;
}
protected override Vector2 GetCilppingMaskPivot()
{
return new Vector2(0, bookModels.ClippingPivotY);
}
protected override Vector2 GetPagePivot()
{
return Vector2.right;
}
protected override Vector3 GetValidAngle(float angle)
{
return Vector3.forward * (angle + 180);
}
}
}
5.新建DragRightPage腳本
代碼如下(示例):
using UnityEngine;
namespace HKZ
{
public class DragRightPage : DragPageBase
{
public DragRightPage(UIBook uIBook, BookModels bookModels, TheDraggingPage frontPage, TheDraggingPage backPage, Vector3 startPos)
: base(uIBook, bookModels, frontPage, backPage, startPos)
{
}
protected override Vector3 GetBookCorner()
{
return bookModels.RightCorner;
}
protected override Vector2 GetCilppingMaskPivot()
{
return new Vector2(1, bookModels.ClippingPivotY);
}
protected override Vector2 GetPagePivot()
{
return Vector2.zero;
}
protected override Vector3 GetValidAngle(float angle)
{
return Vector3.forward * angle;
}
}
}
6.新建IDragPage腳本
代碼如下(示例):
using System;
using UnityEngine;
namespace HKZ
{
public interface IDragPage
{
void BeginDragPage(Vector3 point);
void UpdateDrag();
void EndDragPage(Action complete);
}
public abstract class DragPageBase
{
private UIBook uIBook;
protected BookModels bookModels;
private TheDraggingPage frontPage;
private TheDraggingPage backPage;
private Vector3 startPos;
private RectTransform clippingMask;
public DragPageBase(UIBook uIBook, BookModels bookModels,
TheDraggingPage frontPage, TheDraggingPage backPage, Vector3 startPos)
{
this.uIBook = uIBook;
this.bookModels = bookModels;
this.frontPage = frontPage;
this.backPage = backPage;
this.startPos = startPos;
clippingMask = uIBook.GetClippingMask();
}
/// <summary>
/// 開始拖拽
/// </summary>
/// <param name="point"></param>
public void BeginDragPage(Vector3 point)
{
//設定中心點
clippingMask.pivot = GetCilppingMaskPivot();
bookModels.ClickPoint = point;
frontPage.BeginDragPage(startPos, GetPagePivot());
backPage.BeginDragPage(startPos, GetPagePivot());
}
protected abstract Vector2 GetCilppingMaskPivot();
protected abstract Vector2 GetPagePivot();
/// <summary>
/// 拖拽程序
/// </summary>
public void UpdateDrag()
{
bookModels.ClickPoint = uIBook.GetClickPos();
backPage.SetParent(clippingMask, true);
frontPage.SetParent(uIBook.transform, true);
bookModels.CurrentPageCorner = uIBook.CulculateDraggingCorner(bookModels.ClickPoint);
Vector3 bottomCrossPoint = UpdateClippingMask();
UpdateBackSide(bottomCrossPoint);
frontPage.SetParent(clippingMask, true);
frontPage.ResetShadowDate();
frontPage.transform.SetAsFirstSibling();
backPage.SetShadowFollow(clippingMask);
}
private Vector3 UpdateClippingMask()
{
Vector3 bottomCrossPoint;
float angle = uIBook.CulculateFoldAngle(bookModels.CurrentPageCorner, GetBookCorner(), out bottomCrossPoint);
if (angle > 0)
{
angle = angle - 90;
}
else
{
angle = angle + 90;
}
clippingMask.eulerAngles = Vector3.forward * angle;
clippingMask.localPosition = bottomCrossPoint;
return bottomCrossPoint;
}
protected abstract Vector3 GetBookCorner();
private void UpdateBackSide(Vector3 bottonCrossPoint)
{
backPage.transform.position = uIBook.Local2WorldPos(bookModels.CurrentPageCorner);
Vector3 offset = bottonCrossPoint - bookModels.CurrentPageCorner;
float angle = Mathf.Atan2(offset.y, offset.x) * Mathf.Rad2Deg;
backPage.transform.eulerAngles = GetValidAngle(angle);
}
protected abstract Vector3 GetValidAngle(float angle);
public void EndDragPage(Action complete)
{
Vector3 corner;
if (bookModels.CurrentPageCorner.x>bookModels.BottomCenter.x)
{
corner = bookModels.RightCorner;
}
else
{
corner = bookModels.LeftCorner;
}
uIBook.FlipAni(corner, complete);
}
}
}
7.新建Page腳本
代碼如下(示例):
using System;
using UnityEngine;
using UnityEngine.UI;
namespace HKZ
{
public class Page : MonoBehaviour
{
//參考
public Shadow Shadow
{
get;
private set;
}
//組件
private Func<int, Sprite> getSprite;
private Image img_Page;
protected RectTransform rect;
//資料
/// <summary>
/// 頁數
/// </summary>
private int id;
public int ID
{
get
{
return id;
}
set
{
id = value;
ChangeSprite(value);
}
}
/// <summary>
/// 初始化資料
/// </summary>
/// <param name="getSprite"></param>
public virtual void Init(Func<int,Sprite> getSprite)
{
rect = GetComponent<RectTransform>();
this.getSprite = getSprite;
img_Page = GetComponent<Image>();
Shadow = transform.GetChild(0).gameObject.AddComponent<Shadow>();
}
/// <summary>
/// 賦值Sprite
/// </summary>
/// <param name="id"></param>
private void ChangeSprite(int id)
{
img_Page.sprite = getSprite(id);
}
/// <summary>
/// 設定活躍狀態
/// </summary>
/// <param name="isActive"></param>
public void SetActiveState(bool isActive)
{
gameObject.SetActive(isActive);
}
/// <summary>
/// 設定父物體
/// </summary>
/// <param name="parent"></param>
/// <param name="worldPosStays"></param>
public void SetParent(Transform parent,bool worldPosStays)
{
transform.SetParent(parent, worldPosStays);
}
}
}
8.新建Shadow腳本
代碼如下(示例):
using UnityEngine;
using UnityEngine.UI;
namespace HKZ
{
public class Shadow : MonoBehaviour
{
//組件
private RectTransform rect;
private Image image;
private Color defaultColor = new Color(1, 1, 1, 0.5f);
/// <summary>
/// 初始化資料
/// </summary>
/// <param name="size"></param>
public void Init(Vector2 size)
{
rect = GetComponent<RectTransform>();
image = GetComponent<Image>();
rect.sizeDelta = size;//設定大小
image.color = defaultColor;//設定顏色
}
/// <summary>
/// 設定shadow的位置
/// </summary>
/// <param name="target"></param>
public void SetShadowFollow(Transform target)
{
transform.position = target.position;
transform.rotation = target.rotation;
}
public void ResetShadowDate()
{
rect.anchoredPosition = Vector2.zero;
rect.localEulerAngles = Vector2.zero;
}
}
}
9.新建TheDraggingPage腳本
代碼如下(示例):
using System;
using UnityEngine;
namespace HKZ
{
public class TheDraggingPage : Page
{
public override void Init(Func<int, Sprite> getSprite)
{
base.Init(getSprite);
SetActiveState(false);
}
/// <summary>
/// 開始拖動
/// </summary>
/// <param name="pos"></param>
/// <param name="pivot"></param>
public void BeginDragPage(Vector3 pos,Vector2 pivot)
{
SetActiveState(true);
rect.pivot = pivot;
transform.position = pos;
transform.eulerAngles = Vector3.zero;
}
/// <summary>
/// 初始化Shadow的大小
/// </summary>
/// <param name="size"></param>
public void InitShadow(Vector2 size)
{
Shadow.Init(size);
}
/// <summary>
/// 讓shadow跟隨書頁
/// </summary>
/// <param name="target"></param>
public void SetShadowFollow(Transform target)
{
Shadow.SetShadowFollow(target);
}
public void ResetShadowDate()
{
Shadow.ResetShadowDate();
}
}
}
完結,如何?,翻書效果不錯吧,是不是想打賞我點辛苦費
3.總結


轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286494.html
標籤:其他
