如何使用Unity将玩家的移动限制在3D对象的表面?


16

我正在尝试创建类似于Mario Galaxy或Geometry Wars 3的效果,其中当玩家绕过“行星”时,重力似乎会调整,并且不会像重力一样从物体边缘掉落固定在一个方向上。

在此处输入图片说明
(来源:gameskinny.com

几何大战3

我设法通过一种方法实现了与我要寻找的目标相近的方法,即应具有重力的物体将其他刚体吸引到该物体上,但是通过使用Unity的内置物理引擎,通过AddForce等进行运动,我只是无法让运动感觉正确。在没有开始从物体表面飞出的情况下,我无法使玩家足够快地移动,并且我无法找到施加的力和重力之间的良好平衡来适应这一点。我当前的实现是对这里发现的内容的改编

我觉得该解决方案可能仍会使用物理方法将播放器固定在物体上(如果他们要离开表面),但是一旦将播放器接地,便可以通过一种方法将播放器固定在表面上并关闭物理并通过其他方式控制播放器,但我不确定。

我应该采取哪种方法将播放器捕捉到物体表面?请注意,该解决方案应该在3D空间(而不是2D)中工作,并且应该能够使用Unity的免费版本实现。



我什至没有想到要在墙上行走。我看一下是否有帮助。
SpartanDonut 2014年

1
如果该问题专门针对在Unity 3D中执行此操作,则应通过编辑使其更加清晰。(那将不会是那个现有副本的精确副本。)
Anko 2014年

这也是我的一般感觉-我将看看是否可以使该解决方案适应3D并将其发布为答案(或者是否有人可以击败我,我也很满意)。我将尝试更新我的问题以使其更清楚。
SpartanDonut 2014年

可能有帮助:youtube.com/watch?
v

Answers:


7

我设法完成了所需的工作,主要是在此博客文章的帮助下完成了拼图的表面捕捉,并提出了自己的玩家移动和相机想法。

将播放器捕捉到对象表面

基本设置由一个较大的球体(世界)和一个较小的球体(玩家)组成,两个球体都附加有球体碰撞器。

正在完成的大部分工作是通过以下两种方法进行的:

private void UpdatePlayerTransform(Vector3 movementDirection)
{                
    RaycastHit hitInfo;

    if (GetRaycastDownAtNewPosition(movementDirection, out hitInfo))
    {
        Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
        Quaternion finalRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, float.PositiveInfinity);

        transform.rotation = finalRotation;
        transform.position = hitInfo.point + hitInfo.normal * .5f;
    }
}

private bool GetRaycastDownAtNewPosition(Vector3 movementDirection, out RaycastHit hitInfo)
{
    Vector3 newPosition = transform.position;
    Ray ray = new Ray(transform.position + movementDirection * Speed, -transform.up);        

    if (Physics.Raycast(ray, out hitInfo, float.PositiveInfinity, WorldLayerMask))
    {
        return true;
    }

    return false;
}

Vector3 movementDirection参数仅仅是因为它的声音,我们将要移动玩家在这个框架,以及计算矢量,而结束了在这个例子比较简单的方向,是有点棘手,让我在第一弄清楚。稍后再详细介绍,但请记住,它是玩家移动此帧方向上的归一化向量。

逐步进行操作,我们要做的第一件事是检查是否使用起源于脚本的公共LayerMask属性WorldLayerMask的,指向玩家向下向量(-transform.up)的假想未来位置的光线击中了世界。如果您想要更复杂的碰撞或多层,则必须构建自己的图层蒙版。如果光线投射成功击中了某物,则使用hitInfo检索法线和击中点,以计算播放器的新位置和旋转角度,该位置和旋转角度应恰好在对象上。根据所涉及的玩家对象的大小和原点,可能需要抵消玩家的位置。

最后,这实际上只是经过测试,可能仅在简单的物体(例如球体)上能很好地工作。正如我根据解决方案所基于的博客文章所暗示的那样,您可能希望执行多个射线广播,并平均其位置和旋转角度,以便在更复杂的地形上移动时获得更好的过渡效果。在这一点上,我可能还没有想到其他陷阱。

相机和运动

一旦玩家将物体粘在物体表面上,下一个要解决的任务就是移动。我本来是从相对于玩家的移动开始的,但是我开始遇到球体两极的问题,方向突然改变,使我的玩家一遍又一遍地迅速改变方向,以至于我无法越过两极。我最后要做的是让我的玩家相对于相机移动。

满足我需求的最佳方法是拥有一台仅根据玩家位置严格跟随玩家的摄像机。结果,即使摄影机在技术上旋转,向上按压也总是将播放器移向屏幕顶部,向下移至底部,依此类推。

为此,在目标对象是播放器的摄像机上执行以下操作:

private void FixedUpdate()
{
    // Calculate and set camera position
    Vector3 desiredPosition = this.target.TransformPoint(0, this.height, -this.distance);
    this.transform.position = Vector3.Lerp(this.transform.position, desiredPosition, Time.deltaTime * this.damping);

    // Calculate and set camera rotation
    Quaternion desiredRotation = Quaternion.LookRotation(this.target.position - this.transform.position, this.target.up);
    this.transform.rotation = Quaternion.Slerp(this.transform.rotation, desiredRotation, Time.deltaTime * this.rotationDamping);
}

最后,为了移动播放器,我们利用了主摄像头的变换,从而使控件向上,向下移动,向下移动等。在这里,我们调用UpdatePlayerTransform,它将位置锁定到世界对象。

void Update () 
{        
    Vector3 movementDirection = Vector3.zero;
    if (Input.GetAxisRaw("Vertical") > 0)
    {
        movementDirection += cameraTransform.up;
    }
    else if (Input.GetAxisRaw("Vertical") < 0)
    {
        movementDirection += -cameraTransform.up;
    }

    if (Input.GetAxisRaw("Horizontal") > 0)
    {
        movementDirection += cameraTransform.right;
    }
    else if (Input.GetAxisRaw("Horizontal") < 0)
    {
        movementDirection += -cameraTransform.right;
    }

    movementDirection.Normalize();

    UpdatePlayerTransform(movementDirection);
}

要实现更有趣的摄像头,但控件与我们在此处所使用的控件大致相同,则可以轻松实现不渲染的摄像头,或者仅实现另一个虚拟对象的移动基础,然后使用更有趣的摄像头来渲染您希望游戏看起来像。当您在不破坏控件的情况下围绕对象移动时,这将允许很好的相机过渡。


3

我认为这个想法可行:

保留行星网格的CPU端副本。拥有网格顶点也意味着您对行星上的每个点都有法线向量。然后,完全禁用所有实体的重力,而是沿与法向矢量完全相反的方向施加力。

现在,应基于哪一点计算该行星的法向矢量?

最简单的答案(我敢肯定可以正常工作)是类似于牛顿的方法:当物体首次产生时,您知道它们在行星上的所有初始位置。使用该初始位置来确定每个对象的up向量。显然,重力将处于相反的方向(朝向down)。在下一帧中,在施加重力之前,将射线从对象的新位置投射到其旧down矢量。使用该射线与行星的相交作为确定up矢量的新参考。光线未命中的罕见情况意味着发生了严重错误,您应该将对象移回上一帧中的位置。

还要注意,使用这种方法,玩家起源越远,行星的逼近度就越差。因此,最好将每个玩家脚周围的某个地方用作其来源。我在猜测,但我认为将脚用作原点也将使玩家的操作和导航更加轻松。


最后一点:为了获得更好的效果,您甚至可以执行以下操作:跟踪玩家在每一帧中的运动(例如,使用 current_position - last_position)。然后钳住它movement_vector,使其与对象的长度up为零。我们称这个新向量reference_movement。移动前一个reference_pointreference_movement并将此新点用作光线跟踪原点。在(如果)射线击中行星之后,移动reference_point到该击中点。最后,根据此new来计算新的up向量reference_point

总结一下一些伪代码:

update_up_vector(player:object, planet:mesh)
{
    up_vector        = normalize(planet.up);
    reference_point  = ray_trace (object.old_position, -up_vector, planet);
    movement         = object.position - object.old_position;
    movement_clamped = movement - up_vector * dot (movement, up_vector);

    reference_point  += movement_clamped;
    reference_point  = ray_trace (reference_point, -up_vector, planet);
    player.up        = normal_vector(mesh, reference_point);
}

2

这个帖子可能会有所帮助。其要点是,您无需使用字符控制器,而是可以使用物理引擎自行创建。然后,使用播放器下方检测到的法线将它们定向到网格的表面。

这是一个不错的概述是该技术。网路搜寻字词有更多资源,例如「在3d物件上行走马里奥星系的统一性」。

在其中一个场景中,还有一个来自unity 3.x的演示项目,该项目具有行走的引擎,一个士兵和一只狗。它演示了在3D对象“ 银河”风格上行走。被runevision 称为运动系统


1
乍一看,该演示无法很好地运行,并且Unity软件包存在构建错误。我将看看我是否可以完成这项工作,但希望能提供更完整的答案。
SpartanDonut

2

回转

在Answers.unity3d.com(实际上由我自己提出)上签出有关此问题的答案。引用:

第一个任务是获取定义了的向量。从您的图形中,可以用以下两种方法之一来完成它。您可以将行星视为球体并使用(object.position-planet.position)。第二种方法是使用Collider.Raycast()并使用它返回的“ hit.normal”。

这是他向我建议的代码:

var up : Vector3 = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

称每个更新为例,您应该使其正常工作。(请注意,代码在UnityScript中)。C#中
的相同代码:

Vector3 up = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

重力

对于引力,您可以使用我在问题中描述的“行星物理技术”,但实际上并没有进行优化。这只是我想到的事情。
我建议您为重力创建自己的系统。

这是Sebastian Lague的Youtube教程。这是一个很好的解决方案。

编辑:以统一的方式,转到“ 编辑” >“ 项目设置” >“ 物理”,并将“ 重力”的所有值设置为0(或从所有对象中删除“ 刚体”),以防止内置重力(仅将播放器拉下)与定制解决方案。(把它关掉)


将玩家吸引到行星的中心(并相应地旋转它们)对于接近球形的行星效果很好,但是我可以想象,对于球形较小的物体,这确实是错误的,例如火箭形状的Mario在第一个GIF中跳跃。
Anko 2014年

您可以创建一个仅限于绕X轴移动(例如,在非球形对象内部)的多维数据集,并在播放器移动时移动它,使其始终在播放器下方一直笔直,然后拉动播放器下降到立方体。尝试一下。
Daniel Kvist 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.