** unity实现3d摇杆 结合leapmotion控制实现VR场景移动 **
Created by miccall (转载请注明出处)
开发vr很恼火的就是场景的移动 真实地方本来就小,如果vr里面的场景也小,那还玩个啥,所以虚拟现实的场景是很大的 如何实现小空间对大空间的移动,这也是vr开发要解决的问题之一 。
我们老大设想了摇杆操作 我就顺着思路做了一套 先看看基本效果
- 当手移动中间的球时候 ,就像触碰摇杆一样 ,可以向四周摇动 。
外面的环是一个指示器 当球触发到环的时候,给一个颜色的变化,让用户知道自己将朝哪一个方向运动 。
下面是一个围棋场地 用来感受自己在环境中的移动。
摄像机 包括controller都在一个游戏物体上面 也很好移植 ,下面就一步步来展示这个的制作过程 。
3d摇杆的基本框架
- 首先建立一个MianPivot的空物体 它代表着游戏人物 到时候可以自己设定 。
- 其下呢 ,一个是VR视角 可以选用官方的LMHeadMountedRig 这个预制体 。
- 另一个就是我门要做的这个摇杆了 。
- 摇杆分为三部分 BoundaryRing 就是外面的一个指示环 。root是一个中心点 用来归位 sphere是一个移动的球,用手来触碰它 让它在空间中移动,实现摇杆效果 。
实现思路
看过摇杆的自动归位 ,一开始是拿弹簧力来实现,但是弹簧回弹的惯性极其的难掌控 ,来回摆动简直让人抓狂 。于是, 果断放弃了unity的弹簧力 改自己写插值移动 ,让它在手不触碰他的时候,就让它移动到root这个点 。
大概一分析就是这么简单的东西了,但是写起来还是比较烦的,bug极其的多,如何判断手的hold 如何判断移动 回弹的触发条件 等等一系列 做出好的效果还是很不容易的。
还有就是状态的类,球是否是移动的状态 手是否是hold的状态 这个也得写个类来管理
那么物体上呢 ,就很简单了 加一个触发器 当触发的时候 改变他们的状态 。
大家自己建立两个球(一个root点 一个移动的球)并把他们放到一个父物体下面,就可以开始写脚本了。
开始写脚本
- 先在父类上面建立一个Controller脚本 用来管理游戏移动对象, root和sphere 。
好 那就先这样写
public class springController : MonoBehaviour
{
[Tooltip("Navigation Controls the walking game object")]
public GameObject player;
[Tooltip("Used to move the navigation ball")]
public GameObject sphere;
[Tooltip("The root node of the navigation (reset point)")]
public GameObject root;
Vector3 sphereposition;
Vector3 rootposition;
float speed = 5f; // 导航球复位的速度
// Use this for initialization
void Start()
{
//获取 初始位置
sphereposition = sphere.transform.localPosition;
rootposition = root.transform.localPosition;
}
// Update is called once per frame
void Update()
{
//更新 导航球 的位置
sphereposition = sphere.transform.localPosition;
Movecheck(); //判断并控制player的移动
setstop(); //导航球归位
}
void setstop()
{
if (HandState.Handstate == HandState.HandStateRelease)
{
float step = speed * Time.deltaTime;
sphere.GetComponent().velocity = Vector3.zero;
sphere.transform.localPosition = new Vector3(Mathf.Lerp(sphereposition.x, rootposition.x, step), Mathf.Lerp(sphereposition.y, rootposition.y, step), Mathf.Lerp(sphereposition.z, rootposition.z, step));//插值算法也可以
if (SphereState.Spherestate == SphereState.Spherestateclosed)
sphere.transform.localPosition = new Vector3(0, 0, 0);
}
}
void debugshpereposition()
{
print("sphereposition" + sphereposition);
}
void Movecheck()
{
if(HandState.Handstate == HandState.HandStateHold)
{
startfollow();
}
else if(SphereState.Spherestate == SphereState.Spherestateclosed)
{
Stopfollow();
}
}
void startfollow()
{
transform.parent.GetComponent().enabled = true;
}
void Stopfollow()
{
transform.parent.GetComponent().enabled = false;
}
void OnTriggerExit(Collider other)
{
if (issphere(other))
{
HandState.Handstate = HandState.HandStateRelease;
setstop();
}
}
bool issphere(Collider other)
{
return other.transform.name == "Sphere";
}
}
然后就是root和sphere上面 各有一个triiger判断的类
public class rootTrigger : SphereState
{
Collider currentcollider;
enum currentstate
{
colse,move
}
currentstate state;
void OnTriggerEnter(Collider collider)
{
currentcollider = collider;
state = currentstate.colse;
//spheredebug(collider, "in");
}
void OnTriggerExit(Collider collider)
{
currentcollider = collider;
state = currentstate.move;
//spheredebug(collider, "out");
}
public override bool isclosed()
{
if (Isphere(currentcollider))
{
if (state == currentstate.colse)
return true;
else return false;
}
return false;
}
private bool Isphere(Collider collider)
{
return collider.transform.name == "Sphere" ;
}
void spheredebug(Collider collider,string state)
{
if (Isphere(collider))
{
print("Sphere"+" "+ state);
}
}
}
第二个
public class HnadTrigger : HandState
{
int handcount = 0 ;
//public Text handcounttext ;
void OnTriggerEnter(Collider other)
{
if(IShand(other))
{
//handdebug(other, "in");
handcount++;
}
}
void OnTriggerExit(Collider other)
{
if(IShand(other))
{
//handdebug(other, "out");
handcount--;
}
}
bool IShand(Collider other)
{
string Collidername = other.transform.parent.name;
if (Collidername == "thumb") return true;
else if (Collidername == "index") return true;
else if (Collidername == "middle") return true;
else if (Collidername == "thumb") return true;
else if (Collidername == "RigidRoundHand_L" || Collidername == "RigidRoundHand_R") return true;
else return false;
}
void handdebug(Collider other, string state)
{
print("hand" + state+" ++++++++++++" + Time.time);
}
public override bool ishold()
{
//print(" ishold() child ");
//handcounttext.text = " count"+handcount;
if (handcount != 0)
{
//print("is hold");
return true;
}
return false;
}
}
这个hold的状态也是搞了我很久 没办法处理判断hold和release两个状态
最后想到的用碰撞器的数量来判断吧 ,当进入trigger就加加 出去就减减 最后数量为0 那么就是松开状态了仅供参考
状态脚本
public class HandState : MonoBehaviour {
public static int HandStateHold = 0 ;
public static int HandStateRelease = 1;
public static int Handstate = HandStateRelease ;
public virtual bool ishold()
{
return true;
}
void setvalue()
{
if (ishold())
{
Handstate = HandStateHold;
}
else
StartCoroutine(WaitAndPrint(0.5F));
}
IEnumerator WaitAndPrint(float waitTime)
{
yield return new WaitForSeconds(waitTime);
//等待之后执行的动作
Handstate = HandStateRelease;
}
void Update()
{
setvalue();
}
}
public class SphereState : MonoBehaviour {
public static int Spherestateclosed = 0;
public static int Spherestatemove = 1;
public static int Spherestate = Spherestateclosed;
public virtual bool isclosed()
{
return false;
}
void setvalue()
{
if (isclosed())
{
Spherestate = Spherestateclosed;
}
else
{
Spherestate = Spherestatemove;
}
}
void Update()
{
setvalue();
}
}
- 这个就比较简单了 也就不多解释了 有想要源码的 。
地址 密码 1tcq
4月更新 -重新构建了代码 可能与上面介绍的很大不同 以后会更新介绍 下面是使用方法 -
导航球组件 使用说明
一.demo讲解
- cube 挂了一个自动化代码 ,可以生成一个楼梯的demo
- plane是地面 。
- newNave就是导航球了
- FPSController是游戏物体 ,全称第一人称游戏控制器
二 . 导航球组件使用说明
- 注意 : 初始化的时候 ,世界坐标必须为000,否侧初始化之后,导航球的位置会有所偏差,且偏差严重 。
- Creathuan (c#) : 默认开启 keep default。它用来生成导航球所必要的外部指示器 。需要的参数 ,Bb (一个prefabs 导航球的一个边) Bb1 (root中心点 集成在newNave 中 也许要赋值 ) Parent_ring (生成一圈Bb时,将会把他们移动到该gameobject下面 集成在newNave 中 也许要赋值)
- move (c#) : 默认关闭 有代码必要时启动 ,他只有一个start()方法 。用来将newNave移动到某个位置 。它有两个用法 ,你可以指定一个空物体,并给它赋值到init_position_obj参数中 ,这样在启用该脚本时,就可以初始化到这个空物体的位置 ,如果你不指定这个位置,他会根据第二个参数,Vector3 init_position来初始化到这个位置 ,为了避免初始化位置找不到,必须指定一个位置 。
-
- 注意 move 这个脚本是由 Creathuan( c# )这个组件初始化环结束后启用 。你也可以自定义启用 。
- playercontroller ( c# ) : 默认关闭 ,keep default 。这个组件由代码启动。只需在没有赋值的情况下给他赋值 。 sp(导航球的滑动球 集成在newNave 中 也许要赋值) root(导航球的中心点 集成在newNave 中 也许要赋值 ) ALL (导航球的滑动时,所移动的所有物体 自定义赋值 )speed(导航球滑动时,所有物体移动的速度 默认 0.5f)
子物体介绍
- navigation 中心物体 包含一个sphere和一个root
- sphere 是leaphand可以滑动的一个球 参数 Sphere (他自己本身 )speed( 复位的速度 ) Root (中心点 )provider(leap )Ct(newNave的creathuan脚本,用来获取半径 )
- root是看不见的中心点
- ring 指示器 (具体部件 由代码生成 )
- 好了 ,今天就将怎么多啦 ,祝自己新年快乐。