目录
🟥 本节目标
通过跟踪头部运动,设置一个代表光标的准星,碰触物体后改变位置和朝向,贴近被凝视物体。物体a被凝视一定时间后消失,物体b被凝视一定时间后被击中
🟧 实现原理
1、基于射线原理,Update—>Raycast
2、准星或十字线设置为相机子物体,等待操作的动画为圆环逐渐填满
3、被凝视的可是UI,也可是3D物体
4、击中物体一段时间后,可完成相关操作,如消失、缩放、材质变换等
5、元素一般分三种状态:准星进入、准星停留、准星退出(Collider)
🟨 实现步骤
1️⃣ Canvas
建立Canvas,Render Mode设置为World Space,缩放合适比例0.003
2️⃣ 拖入[CameraRig]
3️⃣ 要交互的UI和3D物体设置
a、Cube添加Rigidbody
b、UI添加BoxCollider,并调整合适大小
c、记得给要交互的物体添加Tag——gazeUI、gazeObj。在VRGazeItem代码我们要使用到他们。
4️⃣ 添加准星
添加准星,将准星Canvas放在[CameraRig]——Camera(head)——Camera(eye)下,Render Mode为World Space,实现如下效果
原理:两个Image,sourceimage都用小圆圈图片,显示方式为旋转显示
5️⃣ GazeController
代码GazeController,实现击中物体时UI的变化,挂载到Camera(eye)上(因为从眼睛这发出射线)
using UnityEngine;
using UnityEngine.UI;
public class GazeController : MonoBehaviour {
//设置canvas位置、朝向
public Canvas reticleCanvas;
//设置准星填充效果等
public Image reticleImage;
//被击中的物体
private GameObject target;
//准星初始位置
private Vector3 originPos;
//准星初始缩放:当看远处时变大,看近处时变小
private Vector3 originScale;
//倒计时时间
private float countDownTime = 3;
//逝去时间(当前时间)
private float nowTime = 0;
void Start () {
reticleImage.fillAmount = 0;
originPos = reticleCanvas.transform.localPosition;
originScale = reticleCanvas.transform.localScale;
}
void Update () {
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hit;
if(Physics.Raycast(ray,out hit, 100))
{
//将准星放到碰撞点上
reticleCanvas.transform.position = hit.point;
reticleCanvas.transform.localScale = originScale * hit.distance;
//让准星的法线方向和被击中的物体法线方向一致
reticleCanvas.transform.forward = hit.normal;
//视线初次进入处理
if (hit.transform.gameObject != target)
{
//视线凝视的上一个物体,完成退出操作
if (target != null)
{
VRGazeItem oldItem = target.GetComponent<VRGazeItem>();
if (oldItem)
{
oldItem.OnGazeOut();
}
}
//视线正处于的物体
target = hit.transform.gameObject;
VRGazeItem newItem = target.GetComponent<VRGazeItem>();
if (newItem)
{
newItem.OnGazeIn();
}
}
//视线长久停留处理
else
{
nowTime += Time.deltaTime;
//正在读秒时间
if (countDownTime > nowTime)
{
reticleImage.fillAmount = nowTime / countDownTime;
}
//达到激活条件
else
{
VRGazeItem gazeFireItem = target.GetComponent<VRGazeItem>();
if (gazeFireItem)
{
gazeFireItem.OnGazeFire(hit);
}
nowTime = 0;
}
}
}
else
{
reticleCanvas.transform.localPosition = originPos;
reticleCanvas.transform.localScale = originScale;
//在未击中时,准星法线方向和摄像机视线方向一致
reticleCanvas.transform.forward = Camera.main.transform.forward;
reticleImage.fillAmount = 0;
}
}
}
6️⃣ VRGazeItem
该代码挂载到被击中的物体上,实现该物体被击中时的效果
与该代码配套的是两个material:HighlightMat和NormalMat,实现3D物体被凝视时材质的变换
using UnityEngine;
using UnityEngine.EventSystems;
public class VRGazeItem : MonoBehaviour {
public Material highlightMat;
public Material normalMat;
//视线进入
public void OnGazeIn()
{
if (gameObject.tag == "gazeUI")
{
//VR场景中UI实现鼠标移入效果
ExecuteEvents.Execute(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerEnterHandler);
}else if (gameObject.tag == "gazeObj")
{
gameObject.GetComponent<Renderer>().material = highlightMat;
}
}
//视线移出
public void OnGazeOut()
{
if (gameObject.tag == "gazeUI")
{
//VR场景中UI实现鼠标移出入效果
ExecuteEvents.Execute(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerExitHandler);
}
else if (gameObject.tag == "gazeObj")
{
gameObject.GetComponent<Renderer>().material = normalMat;
}
}
//凝视处理函数
public void OnGazeFire(RaycastHit hit)
{
if (gameObject.tag == "gazeUI")
{
gameObject.SetActive(false);
}
else if (gameObject.tag == "gazeObj")
{
gameObject.GetComponent<Rigidbody>().AddForceAtPosition(hit.point.normalized * 100, hit.point);
}
}
}
7️⃣ 为准星添加shader
该名为UIOverlay的shader解决了准星面片贴近物体表面闪烁问题
将添加了该shader的material赋值到准星和准星的背景板上即可。
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "UI/Overlay"
{
Properties
{
[PerRendererData] _MainTex ("Font Texture", 2D) = "white" {}
_Color("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
}
SubShader
{
LOD 100
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas" = "True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest Always
Offset -1, -1
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityUI.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float4 color : COLOR;
};
struct v2f
{
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
fixed4 color : COLOR;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed4 _TextureSampleAdd;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
o.color = v.color * _Color;
#ifdef UNITY_HALF_TEXEL_OFFSET
o.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = (tex2D(_MainTex, i.texcoord) + _TextureSampleAdd) * i.color;
clip (col.a - 0.01);
return col;
}
ENDCG
}
}
}
大家还有什么问题,欢迎在下方留言!
如果你有 技术的问题 或 项目开发
都可以加下方联系方式
和我聊一聊你的故事🧡