根据Shader动态生成遮罩

源码地址

圆形遮罩镂空处理脚本:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; /// <summary>
/// 圆形遮罩镂空
/// </summary>
public class CircleGuidance : MonoBehaviour
{
public static CircleGuidance instance;
/// <summary>
/// 高亮显示目标
/// </summary>
public Image target;
/// <summary>
/// 区域范围缓存
/// </summary>
private Vector3[] corners = new Vector3[];
/// <summary>
/// 镂空区域中心
/// </summary>
private Vector4 center;
/// <summary>
/// 镂空区域半径
/// </summary>
private float radius;
/// <summary>
/// 遮罩材质
/// </summary>
private Material material;
/// <summary>
/// 当前高亮区域半径
/// </summary>
private float currentRadius;
/// <summary>
/// 高亮区域缩放的动画时间
/// </summary>
private float shrinkTime = 0.5f;
/// <summary>
/// 事件渗透组件
/// </summary>
private GuidanceEventPenetrate eventPenetrate;
private void Awake()
{
instance = this;
}
public void Init(Image target)
{
this.target = target;
eventPenetrate = GetComponent<GuidanceEventPenetrate>();
if (eventPenetrate != null)
{
eventPenetrate.SetTargetImage(target);
}
Canvas canvas = GameObject.Find("Canvas").GetComponent<Canvas>();
//获取高亮区域的四个顶点的世界坐标
target.rectTransform.GetWorldCorners(corners);
//计算最终高亮显示区域的半径
radius = Vector2.Distance(WorldToCanvasPos(canvas, corners[]), WorldToCanvasPos(canvas, corners[])) / ;
//计算高亮显示区域的中心
float x = corners[].x + ((corners[].x - corners[].x) / );
float y = corners[].y + ((corners[].y - corners[].y) / );
Vector3 centerWorld = new Vector3(x, y, );
Vector2 center = WorldToCanvasPos(canvas, centerWorld);
//设置遮罩材质中的中心变量
Vector4 centerMat = new Vector4(center.x, center.y, , );
material = GetComponent<Image>().material;
material.SetVector("_Center", centerMat);
//计算当前高亮显示区域的半径
RectTransform canRectTransform = canvas.transform as RectTransform;
if (canRectTransform != null)
{
//获取画布区域的四个顶点
canRectTransform.GetWorldCorners(corners);
//将画布顶点距离高亮区域中心最近的距离昨晚当前高亮区域半径的初始值
foreach (var corner in corners)
{
currentRadius = Mathf.Max(Vector3.Distance(WorldToCanvasPos(canvas, corner), corner), currentRadius);
}
}
material.SetFloat("_Slider", currentRadius);
}
/// <summary>
/// 收缩速度
/// </summary>
private float shrinkVelocity = 0f;
private void Update()
{
//从当前半径到目标半径差值显示收缩动画
float value = Mathf.SmoothDamp(currentRadius, radius, ref shrinkVelocity, shrinkTime);
if (!Mathf.Approximately(value, currentRadius))
{
currentRadius = value;
material.SetFloat("_Slider", currentRadius);
}
} /// <summary>
/// 世界坐标转换为画布坐标
/// </summary>
/// <param name="canvas">画布</param>
/// <param name="world">世界坐标</param>
/// <returns></returns>
private Vector2 WorldToCanvasPos(Canvas canvas, Vector3 world)
{
Vector2 position;
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, world, canvas.GetComponent<Camera>(), out position);
return position;
}
}

矩形遮罩镂空处理脚本:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; /// <summary>
/// 矩形遮罩镂空
/// </summary>
public class RectGuidance : MonoBehaviour
{
public static RectGuidance instance;
/// <summary>
/// 高亮显示目标
/// </summary>
public Image target;
/// <summary>
/// 区域范围缓存
/// </summary>
private Vector3[] corners = new Vector3[];
/// <summary>
/// 镂空区域中心
/// </summary>
private Vector4 center;
/// <summary>
/// 最终的偏移x
/// </summary>
private float targetOffsetX = ;
/// <summary>
/// 最终的偏移y
/// </summary>
private float targetOffsetY = ;
/// <summary>
/// 遮罩材质
/// </summary>
private Material material;
/// <summary>
/// 当前的偏移x
/// </summary>
private float currentOffsetX = 0f;
/// <summary>
/// 当前的偏移y
/// </summary>
private float currentOffsetY = 0f;
/// <summary>
/// 高亮区域缩放的动画时间
/// </summary>
private float shrinkTime = 0.5f;
/// <summary>
/// 事件渗透组件
/// </summary>
private GuidanceEventPenetrate eventPenetrate; private void Awake()
{
instance = this;
}
public void Init(Image target)
{
this.target = target;
eventPenetrate = GetComponent<GuidanceEventPenetrate>();
if (eventPenetrate != null)
{
eventPenetrate.SetTargetImage(target);
}
Canvas canvas = GameObject.Find("Canvas").GetComponent<Canvas>();
//获取高亮区域的四个顶点的世界坐标
target.rectTransform.GetWorldCorners(corners);
//计算高亮显示区域在画布中的范围
targetOffsetX = Vector2.Distance(WorldToCanvasPos(canvas, corners[]), WorldToCanvasPos(canvas, corners[])) / 2f;
targetOffsetY = Vector2.Distance(WorldToCanvasPos(canvas, corners[]), WorldToCanvasPos(canvas, corners[])) / 2f;
//计算高亮显示区域的中心
float x = corners[].x + ((corners[].x - corners[].x) / );
float y = corners[].y + ((corners[].y - corners[].y) / );
Vector3 centerWorld = new Vector3(x, y, );
Vector2 center = WorldToCanvasPos(canvas, centerWorld);
//设置遮罩材质中的中心变量
Vector4 centerMat = new Vector4(center.x, center.y, , );
material = GetComponent<Image>().material;
material.SetVector("_Center", centerMat);
//计算当前高亮显示区域的半径
RectTransform canRectTransform = canvas.transform as RectTransform;
if (canRectTransform != null)
{
//获取画布区域的四个顶点
canRectTransform.GetWorldCorners(corners);
//计算偏移初始值
for (int i = ; i < corners.Length; i++)
{
if (i % == )
{
currentOffsetX = Mathf.Max(Vector3.Distance(WorldToCanvasPos(canvas, corners[i]), center), currentOffsetX);
}
else
{
currentOffsetY = Mathf.Max(Vector3.Distance(WorldToCanvasPos(canvas, corners[i]), center), currentOffsetY);
}
}
}
//设置遮罩材质中当前偏移的变量
material.SetFloat("_SliderX", currentOffsetX);
material.SetFloat("_SliderY", currentOffsetY);
}
/// <summary>
/// 收缩速度
/// </summary>
private float shrinkVelocityX = 0f;
private float shrinkVelocityY = 0f;
private void Update()
{
//从当前偏移量到目标偏移量差值显示收缩动画
float valueX = Mathf.SmoothDamp(currentOffsetX, targetOffsetX, ref shrinkVelocityX, shrinkTime);
float valueY = Mathf.SmoothDamp(currentOffsetY, targetOffsetY, ref shrinkVelocityY, shrinkTime);
if (!Mathf.Approximately(valueX, currentOffsetX))
{
currentOffsetX = valueX;
material.SetFloat("_SliderX", currentOffsetX);
}
if (!Mathf.Approximately(valueY, currentOffsetY))
{
currentOffsetY = valueY;
material.SetFloat("_SliderY", currentOffsetY);
}
} /// <summary>
/// 世界坐标转换为画布坐标
/// </summary>
/// <param name="canvas">画布</param>
/// <param name="world">世界坐标</param>
/// <returns></returns>
private Vector2 WorldToCanvasPos(Canvas canvas, Vector3 world)
{
Vector2 position;
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, world, canvas.GetComponent<Camera>(), out position);
return position;
}
}

新手引导管理脚本,通过此脚本管理遮罩跟引导步骤,动态添加按钮点击事件等:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; /// <summary>
/// 新手引导管理
/// </summary>
public class GuideManagers : MonoBehaviour
{
/// <summary>
/// 引导步骤数组(如:第一步-》第二步。。。。)
/// </summary>
public List<GuideUIList> guideList = new List<GuideUIList>();
/// <summary>
/// 当前数组索引
/// </summary>
private int currentIndex = ;
/// <summary>
/// 是否完成所有的新手引导
/// </summary>
private bool isFinish = false;
/// <summary>
/// 遮罩对象
/// </summary>
private GameObject maskPrefabs;
/// <summary>
///
/// </summary>
public void Next()
{
if (isFinish || currentIndex > guideList.Count)
{
return;
}
//注销上一步按钮点击事件
if (currentIndex != && guideList[currentIndex - ].go.GetComponent<EventTriggerListener>() != null)
{
EventTriggerListener.GetListener(guideList[currentIndex - ].go).onClick -= null;
} if (maskPrefabs == null)
{
maskPrefabs = Instantiate(Resources.Load<GameObject>("RectGuidance_Panel"), this.transform);
}
//初始化遮罩
maskPrefabs.GetComponent<RectGuidance>().Init(guideList[currentIndex].go.GetComponent<Image>()); ; currentIndex++;
//给当前按钮添加点击事件
if (currentIndex < guideList.Count)
{
EventTriggerListener.GetListener(guideList[currentIndex - ].go).onClick += (go) =>
{
Next();
};
}
//最后一个按钮点击事件处理
else if (currentIndex == guideList.Count)
{
EventTriggerListener.GetListener(guideList[currentIndex - ].go).onClick += (go) =>
{
maskPrefabs.gameObject.SetActive(false);
//注销最后一个按钮的点击事件
EventTriggerListener.GetListener(guideList[currentIndex - ].go).onClick -= null;
};
isFinish = true;
}
}
}
/// <summary>
/// 引导ui数组
/// </summary>
[Serializable]
public class GuideUIList
{
/// <summary>
/// 引导步骤对象
/// </summary>
public GameObject go; public GuideUIList(GameObject go)
{
this.go = go;
}
}

Unity 新手引导的更多相关文章

  1. Unity3D新手引导开发手记

    最近开始接手新手引导的开发,记录下这块相关的心得 首先客户端是Unity,在接手前,前面的同学已经初步完成了新手引导框架的搭建,这套框架比较简单,有优点也有缺点,稍后一一点评 我们的新手引导是由一个个 ...

  2. NGUI的新手引导的实现

    先声明一下,UNITY新手,如果说的有不对的地方,欢迎各位大神指正. 最近在项目需要实现新手引导,最基础的需求就是需要一个带黑色遮罩的引导UI,类似下图这种: 对,就是这么敷衍的UI,因为是我随手做的 ...

  3. 关于Unity中NGUI的Tab商城、Scrollview和打字机效果的实现

    Tab商城实例 UIToggle 和 UIToggledObjects+ Box Collider(实现商城功能必备) 1.创建两个个UI Sprite,Sprite1和Sprite2 2.给Spri ...

  4. (转)Unity3D新手引导开发手记

    转自:http://www.cnblogs.com/ybgame/p/3844315.html 最近开始接手新手引导的开发,记录下这块相关的心得 首先客户端是Unity,在接手前,前面的同学已经初步完 ...

  5. Unity 游戏框架:UI 管理神器 UI Kit

    UI Kit 快速入门 首先我们来进行 UI Kit 的快速入门 制作一个界面的,步骤如下: 准备 生成代码 逻辑编写 运行 1. 准备 先创建一个场景 TestUIHomePanel. 删除 Hie ...

  6. Unity3d入门 - 关于unity工具的熟悉

    上周由于工作内容较多,花在unity上学习的时间不多,但总归还是学习了一些东西,内容如下: .1 根据相关的教程在mac上安装了unity. .2 学习了unity的主要的工具分布和对应工具的相关的功 ...

  7. 聊聊Unity项目管理的那些事:Git-flow和Unity

    0x00 前言 目前所在的团队实行敏捷开发已经有了一段时间了.敏捷开发中重要的一个话题便是如何对项目进行恰当的版本管理.项目从最初使用svn到之后的Git One Track策略再到现在的GitFlo ...

  8. Unity游戏内版本更新

    最近研究了一下游戏内apk包更新的方法. ios对于应用的管理比较严格,除非热更新脚本,不太可能做到端内大版本包的更新.然而安卓端则没有此限制.因此可以做到不跳到网页或应用商店,就覆盖更新apk包. ...

  9. Unity 序列化

    Script Serialization http://docs.unity3d.com/Manual/script-Serialization.html 自定义序列化及例子: http://docs ...

随机推荐

  1. 使用viper 进行golang 应用的配置管理

    viper 是一个强大的golang 配置管理包,支持多种配置格式内容的读取,同时支持读取key/value 存储的数据 而且不只是读取内容 ,同时也包含了,配置的写入操作. 以下是一个简单的demo ...

  2. 正睿2019省选附加赛 Day10 (这篇其实已经都咕咕了...)

    目录 2019.3.13 A.算算算(二项式定理 斯特林数) B.买买买 C.树树树 2019.3.13 比赛链接 A.算算算(二项式定理 斯特林数) 题目链接 \(x^k\)可以用二项式定理展开,需 ...

  3. CF241E Flights 题解

    题目 做了一下这道题,突然发现自己忘了差分约束,赶紧复习一下. 设当前有n个变量 a1,a2,...,an ,有若干组限制形如 ai≤aj+k (其中k为常数),则由点j向点i连一条边权为k的边,再从 ...

  4. share memory between guest and nic

    通过硬件的IOMMU,内核提供的共享内存.VFIO可以实现. REF: 1. offical DPDK API Doc, 简书有翻译版 DPDK编程指南(翻译)(一)  (二十七) 2. dpdk v ...

  5. nginx rewrite中的break和last

    两个指令用法相同,但含义不同,需要放到rewrite规则的末尾,用来控制重写后的链接是否继续被nginx配置执行(主要是rewrite.return指令). 示例1:(连续俩条rewrite规则)se ...

  6. shell 命令行参数(getopt和getopts)

    getopt 命令 使用getopt命令,可以解析任何命令行选项和参数,但是用法比较复杂.getopt的命令用法如下: $ getopt --help 用法: getopt optstring par ...

  7. thrift 是rpc协议

    PC(Remote Procedure Call,远程过程调用)是建立在Socket之上的,出于一种类比的愿望,在一台机器上运行的主程序,可以调用另一台机器上准备好的子程序,就像LPC(本地过程调用) ...

  8. 第08组 Alpha冲刺(1/4)

    队名 八组评分了吗 组长博客 小李的博客 作业博客 作业链接 组员1(组长) 过去两天完成了哪些任务 文字/口头描述 11月9号开小会安排alpha冲刺开始的工作,进行任务分工,具体见11.09会议记 ...

  9. nodejs异常处理过程/获取nodejs异常类型/写一个eggjs异常处理中间件

    前言 今天想写一下eggjs的自定义异常处理中间件,在写的时候遇到了问题,这个错误我捕获不到类型?? 处理过程,不喜欢看过程的朋友请直接看解决方法和总结 看一下是什么: 抛出的异常是检验失败异常Val ...

  10. Future home of something quite cool.

    我绑定二级域名的时候显示内页如下,怎么办??一级主域名没问题呀~ Future home of something quite cool.If you're the site owner, log i ...