第三人稱視角游戲的鏡頭全自動控制方案


1 | 全自動跟隨方案的簡介

1.1 方案的選擇


視角的選擇和控制,直接關乎玩家的體驗。由於第三人稱視角,相比第一人稱,更能突出主角的動作行為,視角也更加開闊,已經被越來越多的游戲所采用。不過具體實現的效果,卻不盡相同。


從實現原理上,第三人稱視角實現的核心是鏡頭對主角跟隨的處理機制。通常都會遇到這樣一些問題:

(1)鏡頭的上下左右轉動是否完全交由玩家操控?如果不是,那么鏡頭如何能自動做到比較符合玩家直覺的視角呢?

(2)主角被遮擋時或者鏡頭陷入遮擋物內部時,要如何處理?是否要隱藏或半透明化遮擋物?

(3)什么情況下要拉近鏡頭?

……



如果處理不好,就會嚴重影響玩家的體驗。比如:

(1)在玩家探索場景的時候,頻繁的操作鏡頭轉向,會給玩家帶來較多的操作負擔;如果自動轉動的鏡頭與玩家直覺不是很契合,又會造成玩家不適,如果再提供給玩家手動修正的方式,實際上是給玩家平添了更多的麻煩。

(2)當主角被遮擋時,有的游戲會簡單地處理成直接推近到可以看到玩家的地方,而這樣往往會帶來比較大幅度的鏡頭移動,一不小心就給玩家上了一個“眩暈”效果。

(3)對於遮擋物進行隱藏的體驗是很奇怪的,想象一下,前一秒還看到眼前有一棵樹,突然之間就沒了是什么感覺?但是使用半透明的方案,需要非常細致地處理疊加顯示造成的混亂感,畢竟半透之后和背后的畫面是疊加在一起呈現的,實現起來比較復雜。

基於以上原因,我們開始探索一種全自動的鏡頭跟隨方式,系統可以根據玩家的行為,自動計算出符合玩家直覺的視角並完成調整。這樣就可以讓玩家在探索場景時,不需要額外操作鏡頭,也能持續看到主角;隨着主角的移動,鏡頭會轉向玩家想要看到的方向。這樣就釋放了玩家的一只手,可以設計其它更有意義的操作來豐富游戲性了。

我會詳細介紹這個方案的實現,並附贈完整的實現代碼。可以直接用於游戲的開發,使用時根據自己游戲的特點去配置各項參數,以調試出最佳體驗。當然也可以將方案的幾個處理模塊拆出來單獨使用:僅跟隨,與搖桿配合,處理遮擋物,或者添加更多的鏡頭控制方案,切換使用以適應不同的場合。相關配置參數比較多,可詳見附件里的Unity工程。

當然,為了照顧到更多的讀者,請允許我簡單介紹一下幾種游戲視角及其優缺點,老手們可以直接跳過 1.2 部分。

 



1.2 游戲視角的介紹


第一人稱視角,是以玩家的主觀視角進行游戲,你能看到別人,卻看不到自己的全身。優點是代入感強,缺點是有些場景下不能提供良好的視野,比如躲在障礙物背后就完全看不到對面。比如:《使命召喚》。

第二人稱視角,是以玩家敵人的視角進行游戲,體驗比較特殊,應用也比較少。目前,只在一些游戲的某些關卡中少量使用。

第三人稱視角,是玩家以旁觀者視角進行游戲,主角在游戲屏幕上是可見的,可以突出主角的動作行為。整個前方的視角非常開闊,就算是躲在障礙物后邊,也可以通過邊角位置觀察對面的情況,受到玩家的歡迎,比如:《塞爾達傳說:荒野之息》。

目前市面上的游戲主要以第三人稱視角和第一人稱視角為主,甚至有的游戲可以在第一人稱和第三人稱之間實時切換,比如:《我的世界》。

接下來我們就來具體講講方案的實現。

 

 

2 | 第三人稱視角下全自動跟隨鏡頭的實現

 

 

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<float, Vector3> 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 < collision_distance)
{
    viewport_z = collision_distance;
} 

 

 

至於第五條體驗價值,先賣個關子,文末揭曉。

 



2.3 代碼的適配和調整方案


根據以上操作,全自動的跟隨方案就實現了。比如,讀者在游戲開發中,可能會有一些不一樣的需求,大家根據附贈的代碼(可在第三節文末下載),可以做出符合自己的設置。

本文提供的方案代碼是可以直接用於游戲的,使用時根據自己游戲的特點去配置各項參數,以調試出最佳體驗。當然也可以將方案的幾個處理模塊拆出來單獨使用:僅跟隨、與搖桿配合、處理遮擋物,或者添加更多的鏡頭控制方案,切換使用,以適應不同的場合。

 

 

3 | 方案總結和拓展(附Demo下載)

 

3.1 方案總結


這個方案的邏輯和算法上沒有任何艱深的東西,是一個“盡量用簡單且直接的方式去實現還不錯的效果”的思路踐行。我一直認為做游戲,“着眼於體驗”比“追求技術”更有趣一些,畢竟大多偉大的作品都不是靠“秀肌肉”來完成的。也希望大家能在這個方案里能夠找到屬於自己的一點點啟發,這也是我寫此文的初衷。


 

3.2 方案的拓展


讀完此文,你可能會問,這種全自動的跟隨方案還可以應用到哪些場景呢?

本文提供的第三人稱視角下,鏡頭全自動跟隨的方案在實際的游戲中比較適合場景探索時使用,減少玩家調整鏡頭的操作。

在需要特別專注的戰斗時(鎖敵),鏡頭需要持續跟隨玩家的同時關注敵人,那么鏡頭的轉動應該要去服務於這種戰斗體驗了,此時就應該使用不同的跟隨方案;此方案還可以與玩家手動轉動鏡頭搭配使用,在全自動與手動之間實時切換,以適應更多復雜的情況。


 

3.3 方案的優化


本文方案的技術處理手段采用了“不同屬性分層計算策略”,將相機的“位置”與“角度”兩大屬性用完全不耦合的方式去控制,但是實現的方案又能配合上以達到最終的效果;不滿足於此的朋友們,可以進一步挖掘。比如着眼於“玩家直覺”去設計,在泛“用戶體驗”設計領域都可以嘗試如此思考,比如設計用戶界面,設計戰斗操作和反饋之類的。

在源碼中,我還留下了一個TODO(其實就是第五條體驗價值的實現),有興趣的朋友可以嘗試去完成,當做是一次小小的練習。

// 沒有任何旋轉調整選項的話,如果僅zoom便可見且zoom值在設定的最小值以上,則使用zoom,否則不可見
if (config.viewport_z_min < collision_distance)
{
    viewport_z = collision_distance;
}
else
{
    // TODO 需要處理相機陷入模型的情況(兩次射線找到修正位置),同時還可以通過屏幕指示來告知玩家
    Debug.Log("can't see");
}
 

 

PS:Demo支持版本:Unity 2018.4


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM