第三人称视角游戏的镜头全自动控制方案 您所在的位置:网站首页 arm3切换第三人称视角 第三人称视角游戏的镜头全自动控制方案

第三人称视角游戏的镜头全自动控制方案

2023-10-31 15:06| 来源: 网络整理| 查看: 265

 

 

2.1 核心思想

这种方案的核心指导思想是,尽可能地通过旋转和推近来找到可以看到主角的最佳位置,并通过预先设定,完成全自动的镜头跟踪过程。

您可能会问,什么是最佳位置呢?因为它直接关乎到玩家的体验,我们首先就来定义它:在3D游戏中,玩家对于镜头的运动是比较敏感的,当运动过快或者幅度过大时,都容易造成眩晕感。那么在尽可能能看到主角的情况下,相机运动幅度越小,眩晕感也就越少,那么此时的相机位置也就是“最佳位置”了。

依着这个思路,我们把最佳体验从上到下做一个排列:

1. 相机没有任何转动或推近

2. 相机仅仅是为了避免进入模型内部而进行的推近,没有任何转动

3. 相机有一定的旋转和推近(因为结合了两个因素而可能有无数多个解,我们需要根据转动最少的策略以及限制推近的取值范围来确定唯一解)

4. 相机进行变化量几乎不受限地推近尝试直至看到主角,没有任何转动

5. 看不到主角,只能通过标志提示之类的辅助手段告知玩家主角位置

这些体验当中,1和5都是比较容易理解的,2、3、4我来详细阐述一下:先来分析第2条,因为我们的目标是为了看到主角,而相机避免进入模型内部仅仅是一个“修正”行为,当我们在tick中逐帧处理此类“修正”行为时,大多数情况下相机的运动幅度是很小的,所以“仅仅通过修正行为就可以看到主角了”。因此这一条是有较高体验价值的。

那么第2条和第4条的区别是什么呢?从算法上来看,当第2条成立时,那么它们是等价的。但是当第2条不成立时,第4条也是可能成立的,只是这个推近已经不是“修正”不正常位置的行为了,而是为了“看到主角”而进行的推近尝试,属于“主动策略”,其幅度可能会非常大,所以第4条和第2条虽然都是纯推近,但是将这两种情况分开处理,可以让第4条这种情况作为第2条不成立后的补充策略,其价值排在其后。

再来看看第3条,前面说到第2条属于“修正”行为,而第3条与第4条一样都属于“主动策略”,都是为了看到主角而做出的主动调整相机的行为。为了要区分出第3条与第4条的体验价值高低,那么我们就要从一些具体的实例入手,这样会更容易理解一些。

我们想象在一般的游戏情况下,主角在特定的场景中移动,通常会被一些石头、木桶、树木、墙壁拐角等遮挡。当主角试图向此类遮挡物后方移动时,由于第三人称视角下镜头与遮挡物是有一段距离的,遮挡物也有一定的体积,使用第4条的策略来调整,镜头推近的距离至少是大于遮挡物体积的(往往会大得多),相对调整成本就会很大。

使用第3条的策略来调整,调整成本往往与主角从可以被看见到被遮挡时的移动量呈正相关。比如主角在墙角转弯,镜头只需要水平转动一点即可再次见到主角,而且在逐帧处理时,这种策略带来的调整成本通常都是不大的(特别是镜头与摇杆本身就已经有符合直觉的配合了,这个对于第3条策略也是有帮助的)。

由此,我们就得到了从1到5这样的体验排序,以尽可能提供更好的玩家体验。

 

2.2 实现方案

谈到镜头的跟踪,主要解决以下三个问题:

镜头对主角的跟随 如何能够让镜头进行符合直觉的转动 主角被遮掩时的处理

 

2.2.1 如何让镜头跟随主角

原理很简单,只要设定一个偏移量就可以了,或者最直接能够想到的就是下面这个公式:

camera.transform.position = player.transform.position + offset;

 

效果可以通过在update中执行代码看到。你会立即发现两个问题:

问题1:必须要先把镜头的朝向确认好,并且不能改变

问题2:跟随过程是瞬时的,体验生硬

针对问题1,想要镜头无论在哪个朝向,都能让主角在镜头里固定位置,我们可以使用Camera的一个接口:ViewportToWorldPoint(Vector3 viewport) 。

通过viewport,可以获得指定的屏幕位置以及与相机距离的世界坐标点。利用这个接口,我们就可以根据主角的世界坐标与viewport换算后的世界坐标之间的相对位置,来找到使得两个坐标重合的相机位置。

camera.transform.position = camera.transform.position + (player.transform.position - camera.ViewportToWorldPoint(viewport));

 

针对问题2,获得相机新的位置之后,不直接设置到transform上,而是通过缓动算法计算后则可以让体验更为平滑。

camera.transform.position = Vector3.SmoothDamp(current_p, target_p, ref follow_current_velocity, follow_smooth_time, float.MaxValue, delta_time);

 

 

在下面的视频中,可以看到实现效果。

2.2.2 如何能够让镜头进行符合直觉的转动

这种需求,经常出现在需要通过遥感来控制主角行动的游戏中。什么是符合直觉的转动呢?我们可以思考一个问题,当我们通过遥感控制主角往左行走时,我们是否是希望看到左侧的景象?(这里并不考虑锁定敌人进行战术移动这种特定情形)答案显然是肯定的,通常情况下,角色往那边走,我们就希望看到更多的那边的景象。

这里我们将行为简单的分成:摇杆前后左右,分别调整相机的俯仰(pitch)和左右旋转(yaw)。

//当摇杆往左时,镜头也转来看向左边 var delta_yaw = 0 < joystick.x ? yaw_speed * delta_time : -yaw_speed * delta_time; //当摇杆往右时,镜头也转来看向右边 var delta_pitch = 0 < joystick.y ? -pitch_speed * delta_time : pitch_speed * delta_time;

 

同样,也有一些细节需要处理和优化:调整yaw过于灵敏会导致很难进行直线的前进和后退(因为相机会自动调整yaw值)。针对这个问题,可以设置一个角度高通值,只允许摇杆指针与垂直线角度大于此值时才开始调整yaw值。

如果调整度随摇杆移动进行相对应的变化,体验会更自然一些:比如摇杆指针与垂直线夹角越大,则调整度越大,反之越小。这能让玩家体验到越是偏向左右,相机左右转动就会越快,越是偏向前后,相机左右转动会越慢,甚至是不转动(通过上面提到的角度高通值实现)。

由于镜头跟随主角的实现方式是不受镜头朝向限制的,所以响应摇杆调整镜头朝向与跟随主角能够完美地配合起来,形成比较符合直觉的体验。

在下面的视频中,可以看到实现效果。

2.2.3 主角被遮掩时的处理

在主角被遮挡时,要怎么处理呢?镜头移动过程中,陷入了模型内部,要怎么修正?

常见的做法:半透(全透)+ 推近,将场景中的物体分为两类,第一类用半透(全透)进行处理,这类物体通常较小,往往分布在行动路线上,常见的有树木,小型装饰物等;第二类则通过从主角向摄像机发射线,如果遇到此类物体阻挡,那么则将相机推进到碰撞点位置,这类物体往往较大,常见的有墙壁、大型装饰物等。

本文介绍一种不太一样的做法,这种做法不会将场景中的物体分类,而是全部都用同一种方式来处理。

在面对场景中任何遮挡物时,都用统一的一种方式进行处理,减少了场景编辑师的编辑工作,工程师无需特意实现半透(或隐藏)的效果。对玩家来说也能保持一致的体验,理解成本低。

总体的逻辑思路如下:

// 保持与主相机参数一致性 tick_camera(config, time, delta_time); // 处理摇杆对yaw和pitch的影响 var can_see = tick_joystick(config, time, delta_time); if (!can_see) { // 通过调整yaw或者pitc以及viewport_z,找到可以看到目标的位置 tick_see(config, time, delta_time); } // 根据fvp调整相机位置 tick_follow(config, time, delta_time);

 

这里重点说一下tick_see的逻辑:

// 1. 尝试正常的viewport var see_ret = canSee(config, source_p, target_p, out Vector3 collision_p, out float collision_distance); if (SeeResult.CAN_SEE == see_ret) { // 恢复到正常viewport_z viewport_z = 0; return; } else if (SeeResult.CAN_SEE_SINGLE == see_ret) { viewport_z = collision_distance; return; }

 

上面这一段代码是为了完成体验价值排序里的第一条:相机没有任何转动和推近,以及第二条:相机仅仅是为了避免进入模型内部而进行的推近。

// 找到可以看见目标的位置所需要的角度偏移值 private static float findOutCanSeeAngleOffset( CameraController config, float offset_delta, float offset_limit, System.Func vec_rotate_func, Vector3 source_p, Vector3 target_p, bool negative_rotate, out float collision_distance)

 

然后再通过上面这个接口,从镜头的四个不同旋转方向来找到可以看到主角的相机位置,并记录相应的旋转量和推近量,并通过下面这个计算测试值的方法,得到四个方向的镜头调整成本测试值,测试值越低,代表成本越小。这样就完成了体验价值排序里的第三条:相机有一定的旋转和推近。

// 计算选项的测试值(结合角度偏移值和zoom值,以及它们各自设定的权值) private static float calcChooseTest(CameraController config, float angle_offset, float viewport_z_offset) { var choose_test = float.MaxValue; if (360 > angle_offset) { choose_test = angle_offset * config.choose_angle_test_scale + viewport_z_offset * config.choose_viewport_z_test_scale; } return choose_test; }

 

完成第四条体验价值相对来说比较简单了,这里直接使用了前面的策略计算后留下的一个值即可。

// 没有任何旋转调整选项的话,如果仅zoom便可见且zoom值在设定的最小值以上,则使用zoom if (config.viewport_z_min


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有