Pico Unity XR SDK开发笔记(2)-- 乒乓

1,素材

在unity的asset store上下载一个免费的素材“Low-Poly Table Tennis Set”,先“Add to My Assets”,然后在Unity中下载,Window -> Package Manager,如下图,并import:

打开它的场景,在场景中新建XR Rig(见上一章)。下图是目前项目里的物体(不是最初的,增删了一些):

2,转动Snap Turn

通过手柄的遥感来调整朝向

点击XR Rig对象,在Inspector面板中点击“Add Component”,为XR Rig添加“Snap Turn Provider”组件:

设置Turn Amount,代表推一次遥感转多少度。在Controllers变量中“+”一个元素,用右手柄来赋值,直接将XR Rig下面的“RightHandController”拖到Element0这边即可。

 这就完成了转动,在场景中,通过遥控右手柄的遥感来控制自己的转动朝向,每次转动30度。

extra知识点:

Unity中的UI事件都会有回调函数,可以用AddListener或在inspectior面板中来添加相关事件的回调。但目前没发现pico手柄上按键的回调,都是通过在Update中用controller.inputDevice.TryGetFeatureValue函数来获得按钮的值。直接这样写会有问题,比如按下X按钮,会在多个Update帧中都触发,而通常情况下我们只想一次按钮触发一次。SnapTurnProvider这里面的解法是设定了一个时间间隔:

private void Update()
{           
  // wait for a certain amount of time before allowing another turn.
  if (m_TimeStarted > 0.0f && (m_TimeStarted + m_DebounceTime < Time.time))
  {
      m_TimeStarted = 0.0f;
      return;
  }
  。。。

3,传送Teleportation

通过点击地面将自己传送过去(用常规的方法走过去会产生眩晕,传送是VR中的一个解决方案)

首先给XR Rig添加“Teleportation Provider”和“Locomotion System”组件,并赋值XR Rig,如下:

再给地面Floor对象添加“Teleportation Area”组件并赋值:

4,抓取物体

设置左手柄通过射线抓物体,右手柄近距离抓取物体(更符合实际)。

左手控制器LeftHand Controller上添加XR Ray Interactor即可

右手控制器不用射线,可以删除Line Render和XRInteractor line Visual组件。增加XR Direct Interactor和Sphere Collider。Direct Interactor通过Collider来跟被抓物体交互,所以Collider的半径就是跟物体的接触距离:

 被抓物体添加XR Grab Interactable组件,这边给球拍和球添加,就可以用手抓拍,左手拿球(按手柄的grip按钮)。

可以把球抛在空中,用球拍去打,球需要有Rigidbody重力,我把球拍的Rigidbody重力去掉了。但这时弹力不够

5,添加弹力

新建Physical Material,设置弹力Bounciness

把Physical Material拖拽赋值给球、球拍、球桌、地板、墙面的Collider的Material变量,这样就有了弹力

 我这边设置了多个不同参数的Physical Material,赋值给不同的物体,可自行去调整,符合自己的感觉。

这时可以自己发球了,但是球打出去到处乱跑。想做三个控制:

  • 自己发球:点击按钮,球重置到球台上,便于抓取;
  • 对方发球:点击按钮,让球从对面发过来,就可以接球一次了;
  • AI:对面的球拍可以接球,跟我对打起来(哪怕一个回合也行);

前两个通过unity的UI来做

6,UI

UI还是Unity的这一套东西,只不过可以通过手柄的射线去交互。新建Canvas画布,添加Tracked Device Graphic Raycaster组件;在画布下新建Panel,在Panel下新建两个按钮:

我把UI放在对面的墙上,方便操作

 两个球拍一个作为自己的球拍,一个作为对手AI的球拍,重命名如上面的Hierachy图。新建脚本BatPlayer_AI.cs(只贴主要代码,代码组件中有些物体变量需要在Inspector中去设置,从这边开始就不单独展示了,否则有点繁琐),在这里添加AI的一些脚本,同时把这两个按钮的回调函数也写在这这脚本中先:

public void ServeBall_AI()
{
    ball.transform.position = startTransform_AI.position + new Vector3(Random.Range(-0.5f, 0.5f), 0, 0);
    ball.transform.rotation = startTransform_AI.rotation;
    ball.velocity = new Vector3(0, -force * 0.3f, -force);
    transform.position = startTransform_AI.position + new Vector3(0, 0, 0.4f);
    transform.rotation = Quaternion.Euler(90, 0, 0);
    isCatchBall = false;
}

public void ServeBall_FPS()
{
    ball.transform.position = startTransform_FPS.position;
    ball.velocity = Vector3.zero;
}

然后通过AddListener或直接在Inspector中手动将上面的函数绑定到按钮的OnClick事件上(这边是手动绑定,但用代码添加的方式更好,因为手动绑定不利于管理,而且复制到另一个项目或场景中容易丢失绑定关系)

这边需要去调整代码中发球的参数,保证球正确的发过来,我这边的XR Rig朝向是X轴。现在就可以自己发球了,球跑了可以重置,练习发球;也可以让对方发球练习接球。接下来希望对方也能接住我的球。 

7,AI

方法1:

当球与对面的球桌碰撞后,AI开始工作,选择合适的时机(当球的y方向速度足够小),沿着球运动的反方向(只看X和Z轴,Y轴速度此时也接近0了,先不管)击球,只要控制好力度(还没调好。。),就可以将球打回来,大致代码如下:

void Update()
{
    if(isCatchBall && (ball.velocity.z > 0) && (ball.transform.position.y > minCatchHeight))
    {
        CatchBall_AI();
    }
}

void CatchBall_AI()
{
    if (ball.velocity.y > 0.05f)
    {
        transform.rotation = Quaternion.LookRotation(new Vector3(ball.velocity.z, 0, -ball.velocity.x)) * Quaternion.Euler(90, 0, 0);
        transform.position = ball.transform.position + new Vector3(0, 0, catchB + catchW * ball.transform.position.x);
        //GetComponent<Rigidbody>().MovePosition(ball.transform.position + new Vector3(0.1f, 0, 0));
    }
    else
    {
        GetComponent<Rigidbody>().MovePosition(ball.transform.position);
        isCatchBall = false;
    }
}

isCatchBall变量会在球与球桌碰撞后触发的函数中判断,新建脚本BallController.cs绑在球上,添加碰撞回调函数,这里的enableCatchBall()就是设置isCatchBall变量。

void OnCollisionEnter(Collision collision)
{
    if (collision.gameObject.Equals(table) && (transform.position.z > 0.15f) && (GetComponent<Rigidbody>().velocity.z > 0))
    {
        batPlayerAI.logText.text = "OnCollisionEnter" + transform.position;
        batPlayerAI.enableCatchBall();
    }
    else
    {
        batPlayerAI.disableCatchBall();
    }
}

这里加了一系列的hard code来判断各种触发条件以及调整击球的各种参数,不优雅,不过这只是一个温习untiy的demo。

方法2:

有时间学习一下unity中的强化学习模块ML-Agents,先列个TODO。

8,其他细节

上面记录了主要的一些步骤,细节调的比较多,比较繁琐,没法列全

a,球会穿模甚至飞出屋子

因为球速太快,碰撞检测时已经穿过碰撞体。一个方法是增加墙面地面的collider的厚度,但只能缓解部分问题,这边需要调整球的Rigidbody的Collection Detection方式(其他物体的Rigidbody也调一调):

 b,XR Rig的视野需要设置一下,不然太近的物体看不到,XR Rig/Camera Offset/Main Camera:

 c,一直按着右手柄的grip键来握拍,感觉有点伤手柄,可以写一个脚本,让球拍跟着手柄移动,去掉右手柄的XR Direct Interactor组件,写一个脚本BatPlayer1.cs绑在自己的球拍上,在FixedUpdate中实时更新球拍的位置,这边不能直接设置transform的position和rotation,因为这样物理属性就失效了,要用Rigidbody的相关move函数来让刚体运动,这样手挥拍的时候才能击打球:

private void FixedUpdate()
{
    GetComponent<Rigidbody>().MovePosition(rightHand.position);
    GetComponent<Rigidbody>().MoveRotation(rightHand.rotation * Quaternion.Euler(0, -90, 0));
}

d,CatchBall_AI()和上面(c)的代码中,多了一个乘以一个四元数Quaternion的操作,是因为场景中球拍的XYZ朝向跟实际需要的朝向不一致,所以多一步旋转的操作。

猜你喜欢

转载自blog.csdn.net/dragonchow123/article/details/124618897