核心腳本和對象
在VR場景中,我們需要激活我們正在看著的物體,高亮或者交互。在VRSamples中主要包括三個(gè)腳本:VREyeRaycaster, VRInput, 以及VRInteractiveItem。
VREyeRaycaster
VREyeRaycaster.png
該腳本需要和Main Camera關(guān)聯(lián)。在每次調(diào)用Update()時(shí),該腳本都會(huì)使用Physics.Raycast來投射一條射線,以確認(rèn)該射線是否命中任何collider(碰撞體)。使用該腳本還可以排除特定的Layer。在某些場景中,我們可能為了性能考慮,把所有的可交互對象移到一個(gè)單獨(dú)的層。
如果某個(gè)碰撞體被射線命中,那么該腳本將嘗試在GameObject上找到一個(gè)VRInteractiveItem組件。來看下該類的主要代碼:
// 從相機(jī)位置向前發(fā)射一條射線
Ray ray = new Ray(m_Camera.position, m_Camera.forward);
RaycastHit hit;
//做射線投射,看是否有物體相交
if (Physics.Raycast(ray, out hit, m_RayLength, ~m_ExclusionLayers))
{
VRInteractiveItem interactible = hit.collider.GetComponent(); //獲取相交物體上的VRInteractiveItem組件
m_CurrentInteractible = interactible;
//如果相交物體跟上次的物體不同,那么判斷懸停
if (interactible && interactible != m_LastInteractible)
interactible.Over();
// 停用上次相交物體,該函數(shù)內(nèi)置空
if (interactible != m_LastInteractible)
DeactiveLastInteractible();
m_LastInteractible = interactible;
//有物體相交的話,瞄準(zhǔn)星的位置要更新。在該函數(shù)中,會(huì)根據(jù)距離做縮放;使用相交物體的法線方向做旋轉(zhuǎn)。
if (m_Reticle)
m_Reticle.SetPosition(hit);
//調(diào)用事件處理函數(shù)
if (OnRaycasthit != null)
OnRaycasthit(hit);
}
else
{
//沒有相交物體,置空上次使用的可交互物體
DeactiveLastInteractible();
m_CurrentInteractible = null;
//把瞄準(zhǔn)星放到默認(rèn)位置
if (m_Reticle)
m_Reticle.SetPosition();
}
該類在OnEnable函數(shù)中和OnDisable函數(shù)中會(huì)把VRInteractiveItem上的處理事件函數(shù)與VRInput中的事件綁定起來(訂閱事件)。
VRInput
image2.png
VRInput是個(gè)簡單的類,可以判斷用戶在GearVR上(或是使用DK2時(shí)在PC上)所進(jìn)行的一些簡單操作,比如滑動(dòng)、觸碰、或雙觸。
VRInteractiveItem
我們可以把該組件添加到任何希望在VR場景中進(jìn)行交互的GameObject上,在該物體上需要綁定一個(gè)碰撞體。
交互可視化元素SelectionRadial和SelectionSlider
我們同時(shí)利用了radial選擇條(SelectionRadial),以及選擇滑動(dòng)條(SelectionSlider),這樣用戶就可以按住Fire1來確認(rèn)某個(gè)交互:
image3.png
image4.png
當(dāng)按住輸入鍵時(shí),選擇條會(huì)進(jìn)行填充,并在填充完整后分發(fā)OnSelectionComplete或OnBarFilled事件。關(guān)于此部分的代碼,可以在SelectionRadial.cs和SelectionSlider.cs中找到,并進(jìn)行了詳細(xì)的注釋。
在VR的世界里,從用戶交互的角度看,用戶需要時(shí)刻知道自己在做什么,而且可以掌控一切。通過這種“held input”的確認(rèn)輸入方式,可以確保用戶不會(huì)出現(xiàn)誤操作。
VR Sample項(xiàng)目中的各個(gè)場景分析
先來分析一下各個(gè)場景中對象和腳本的作用
MainMenu場景
MainMenu場景可以認(rèn)為是整個(gè)VR項(xiàng)目的入口,用戶首先在這里選擇目標(biāo)場景,然后進(jìn)入。因此非常重要。該場景中的主要交互如下:
Reticle只有瞄準(zhǔn)星的效果:
image5.png
當(dāng)用戶“凝視”菜單選項(xiàng)時(shí),空白的Selection Radial可見。
image6.png
Selection Radial 填充(當(dāng)用戶“凝視”菜單選項(xiàng),且按下fire1輸入鍵)
image7.png
在整個(gè)示例項(xiàng)目中,都是這樣的風(fēng)格,也就是使用bar和radial以固定的速度進(jìn)行填充。在此建議大家在開發(fā)自己的VR項(xiàng)目時(shí)注意到這一點(diǎn),因?yàn)榻换ピO(shè)計(jì)中的連貫性對用戶很重要,特別是對于VR這種新媒介。
Selector/MenuSelectorChild對象是控制界面下邊的瘦長進(jìn)度條,根據(jù)用戶目前凝視的對象(不同的菜單項(xiàng)),會(huì)把進(jìn)度條移動(dòng)到這些對象的下面,在鼠標(biāo)移進(jìn)移出的時(shí)候有一個(gè)popout的效果,根據(jù)幾個(gè)public字段可以設(shè)置相應(yīng)的動(dòng)畫參數(shù)。該腳本還收集了所有的菜單項(xiàng),用他們的transform來作為目標(biāo)位置的參考。
image8.png
VRCameraFade是用來控制場景之間切換效果的腳本。包含場景淡入淡出的效果。Fade效果有兩種,一種是FadeIn,從全黑色逐漸變?yōu)闊o色透明;一種是FadeOut,從無色透明逐漸變?yōu)槿谏?。該組件public的字段中,F(xiàn)adeColor默認(rèn)是全黑色,即FadeIn中的起始顏色和FadeOut中的終止顏色,該顏色會(huì)作用于FadeImage的顏色。另外一個(gè)顏色是代碼中寫死的,即無色透明。FadeDuration是指fade效果的持續(xù)時(shí)間。FadeInOnSceneLoad指的是當(dāng)場景加載的時(shí)候,啟用Fade效果,在Unity的OnLevelWasLoaded回調(diào)中會(huì)使用到。FadeInOnStart和上面的作用類似,但是它控制的是Fade效果在Start回調(diào)中使用。
image10.png
Menu/Items對象及其子對象
MenuButton處理一個(gè)VRInteractiveItem和交互界面(瞄準(zhǔn)星)的事件。主要是三個(gè)事件處理函數(shù):HandleOver(reticle進(jìn)入)、HandleOut(reticle移出)、HandleSelectionComplete(選擇完成)。當(dāng)redial的選擇結(jié)束的時(shí)候,會(huì)調(diào)用HandleSelectionComplete函數(shù),該函數(shù)啟動(dòng)一個(gè)Coroutine,讓當(dāng)前場景淡出,然后加載指定的場景(Scene To Load指定)。
image11.png
MenuItemPopout控制用戶凝視menu item時(shí)popout的效果。
image12.png
MenuAnimator當(dāng)用戶凝視物體的時(shí)候,播放動(dòng)畫(實(shí)際上是一系列的紋理圖片)。
image13.png
SelectionSlider當(dāng)用戶凝視一個(gè)物體、并按住Fire鍵的時(shí)候,用來控制進(jìn)度條的行為。這里的Renderer引用的就是上面提到的瘦長進(jìn)度條,Selection Radial引用的是上面提到的瞄準(zhǔn)星周圍一圈進(jìn)度條。并且通過VRInput對象的回調(diào)來處理輸入的Down和Up事件,Down的時(shí)候開始改變Slider的value,up的時(shí)候清零。通過InteractiveItem對象的回調(diào)來處理可交互對象的slider充滿和瞄準(zhǔn)星懸停事件,這里是播放聲音。
image14.png
多場景通用的對象
WarningTextCanvas是當(dāng)用戶在場景中錯(cuò)誤的輸入之后,在場景中顯示警告信息的界面,采用了world space的canvas界面。該腳本可以設(shè)置警告字符串,引用了VRInput對象,用來處理單擊雙擊等事件。引用了Reticle對象,用它的transform信息來放置警告信息。引用Camera是為了讓警告信息知道朝向哪里。
image15.png
MainCamera這是最重要的一個(gè)對象,包含了上述的很多代碼組件:
VREyeRaycaster, VRInput, SelectionRadial和VRInteractiveItem上面都已經(jīng)介紹過了。
VRCameraUI腳本確保相機(jī)UI能正確的渲染。代碼中把canvas的sortingOrder放到了最大值,然后強(qiáng)制刷新canvas。
Reticle是瞄準(zhǔn)星類。它是屏幕中心的一個(gè)小紅點(diǎn)(顏色可設(shè)定),用來瞄準(zhǔn)當(dāng)前場景中的物體。如果從相機(jī)發(fā)射出來的ray沒有和物體(具有VRInteractiveItem組件)相交的時(shí)候,它在默認(rèn)的位置上;如果有相交物體的話,它的位置在物體的表面。位置的設(shè)定是調(diào)用其中SetPosition函數(shù)進(jìn)行實(shí)現(xiàn)的。Default Distance可以設(shè)置默認(rèn)距離。Use Normal是設(shè)定是否根據(jù)相交物體的法線來設(shè)置Reticle的旋轉(zhuǎn),效果見下面的圖。
image16.png
下圖展示了準(zhǔn)星如何匹配墻壁的法線:
image17.png
下圖展示了準(zhǔn)星如何匹配地板的法線:
image18.png
ReturnToMainMenu用來允許用戶返回主菜單。Menu Scene Name用來設(shè)置場景名稱。引用VRInput對象是為了處理用戶Cancel的輸入。引用VRCameraFade是需要做場景彈出效果。
image19.png
VRTrackingReset是用于使場景重新居中的腳本。里面只有一個(gè)回調(diào)函數(shù)OnApplicationPause,調(diào)用了InputTracking.Recenter()。
image20.png
Intro場景
該場景中包含了通用prefab對象:MainCamera和WarningTextCanvas。場景中包含了三個(gè)主要的介紹界面:How to Use、How to Use Confirm、Return。還有一個(gè)VRDeviceManager對象。所有的調(diào)度控制在System/IntroManager對象下面的IntroManager腳本中。
先從一個(gè)簡單的說起:
VRDeviceManager腳本使用了單例模式,主要用于配置不同的VR平臺(tái)。代碼中使用預(yù)定的宏例如UNITY_ANDROID,進(jìn)行條件編譯。
GUI對象下面是場景中的界面元素,結(jié)構(gòu)如下圖所示。以InstructionsGUI為例進(jìn)行說明。
image21.png
UIFader腳本是用來對一組元素進(jìn)行淡入淡出效果的,并且包含了很多不同的方法。這里把InstructionsGUI的兩個(gè)直接子對象都加到淡入淡出組里面去。還支持設(shè)置淡入淡出速度。
image22.png
PlatformDependentText腳本用來支持不同平臺(tái)的文字組件。
IntroInstructions只是簡單的顯示文字對象。InstructionsSelectionSlider是按鈕狀的滑動(dòng)條對象,包含了好幾個(gè)已經(jīng)熟悉的腳本VRInteractiveItem、SelectionSlider。UITint是第一次見到,它是給圖片染色(tint)的腳本,添加了VRInteractiveItem對象的OnOver、OnOut兩個(gè)事件的處理函數(shù)。當(dāng)瞄準(zhǔn)星與該物體相交的時(shí)候,該腳本根據(jù)Tint變量設(shè)置的顏色,改變圖片的顏色,相當(dāng)于高亮的效果。這里并非完全的顏色替換,而是一種線性加成,計(jì)算顏色的代碼如下:
m_ImagesToTint[i].color += m_Tint * m_TintPercent;
image23.png
GUIArrows腳本用來控制場景中出現(xiàn)的箭頭出現(xiàn)、消失、淡入淡出的效果,VR場景中可以使用這種方式提示用戶應(yīng)該看往正確的方向。Fade Duration指定了淡入淡出的時(shí)間;show angle指的是當(dāng)用戶偏離目標(biāo)方向多少角度的時(shí)候,箭頭出現(xiàn);Desired Direction可以設(shè)定一個(gè)transform來規(guī)定目標(biāo)方向,如果是空的話使用的world space下的forward;引用相機(jī)對象是為了獲取當(dāng)前的朝向;Arrow Renderers是要渲染的對象。
image24.png
IntroManager腳本引導(dǎo)用戶一步一步通過介紹場景。代碼的核心是Coroutine機(jī)制。
image25.png
Flyer場景
Flyer場景是個(gè)計(jì)時(shí)“無盡飛行”的第三人稱游戲,在其中玩家可以通過四處看來引導(dǎo)飛船的方向,并使用Fire1輸入鍵進(jìn)行射擊,通過擊中隕石或是引導(dǎo)飛船穿越空中的門來得分,跟Pilotwings或Starfox這兩款游戲有點(diǎn)類似。
在交互方面,F(xiàn)lyer使用了更簡單的方式,也就是讓FlyerLaserController訂閱VRInput的OnDown事件,從而發(fā)射激光。
Vehicles是場景中的飛機(jī)對象,還包括了飛機(jī)的附屬對象,比如飛機(jī)尾部的噴氣、冒煙的粒子系統(tǒng),飛機(jī)的子彈發(fā)射口,以及用來顯示生命值、時(shí)間、得分的界面元素。
image26.png
FlyerPlayership對象上主要包含了飛機(jī)開火、飛行、爆炸的音效,還有飛機(jī)行為的控制腳本、UI的控制腳本。
FlyerLaserController主要控制飛機(jī)發(fā)射Laser的行為。Laser采用了對象池技術(shù),從池中取對象。引用VRInput是為了處理鼠標(biāo)單擊事件;引用GameController是為了知道當(dāng)前游戲的狀態(tài),游戲沒開始的話,不能發(fā)射laser。LaserObjectPool是用來存放Laser對象的對象池。LaserSpawnPosLeft和LaserSpawnPosRight是Laser發(fā)出的位置和旋轉(zhuǎn),LaserAudio是Laser發(fā)出時(shí)的音效。
image27.png
FlyerMovementController腳本用于控制飛機(jī)的移動(dòng)(飛機(jī)是一直往前飛的,可以使用頭盔控制上下左右活動(dòng))。DistanceFromCamera用于控制相機(jī)和飛機(jī)的距離;Speed控制飛機(jī)飛行的速度;Damping是飛機(jī)運(yùn)動(dòng)時(shí)的阻尼系數(shù);Flyer是飛機(jī)的Transform;TagetMarker是飛機(jī)前面兩個(gè)圓圈標(biāo)記的transform;還引用了Camera和CameraContainer的transform,以及CurrentScore的文本界面。
image28.png
FlyerAlignmentChecker腳本控制的是當(dāng)飛機(jī)接近一個(gè)場景中圓環(huán)Ring的時(shí)候,使得圓環(huán)和飛機(jī)對齊,并改變圓環(huán)的顏色。
image29.png
FlyerHealthController腳本用于控制飛機(jī)的生命狀況,包括生命值,爆炸的prefab,生命條還有音效等。飛機(jī)在爆炸的時(shí)候要隱藏,畢竟飛機(jī)和爆炸的碎片完全是兩個(gè)毫不相關(guān)的物體。
image30.png
System對象下面包含很多游戲控制的對象和腳本,還有對象池,是這個(gè)場景核心的內(nèi)容。
image31.png
FlyerGameController是游戲控制腳本,把整個(gè)游戲流程和其他腳本整合在了一起。
image32.png
EnvironmentController腳本控制了游戲場景中小行星(石頭)和圓環(huán)Ring的生成。
image33.png
UIController腳本對外提供了淡入淡出的效果。
image34.png
此外還有場景中動(dòng)態(tài)生成的物體,Laser有FlyerLaser腳本控制,石頭(小行星)由Asteroid腳本控制、Gate由Ring腳本控制。
Maze場景
Maze(迷宮)游戲中提供了一個(gè)桌面式的交互示例,其中我們可以指引游戲角色到出口,并避免觸發(fā)炮塔(Turret)。在選擇角色的目的地時(shí),會(huì)出現(xiàn)一個(gè)目的地標(biāo)記,同時(shí)還會(huì)顯示一個(gè)角色的路徑。玩家可以通過在觸摸板上使用swipe,按下方向鍵,或是使用游戲操縱桿上的左鍵來旋轉(zhuǎn)視圖。
image35.png
該場景中最主要的還是System和Maze對象。
image36.png
Maze對象包括了整個(gè)場景的元素,墻壁、地面、炮塔、開關(guān)、標(biāo)記等等。
MazeCourse游戲?qū)ο笫且粋€(gè)parent對象,其中包含了MazeFloor和MazeWalls GameObjects,這兩個(gè)對象依次包含了迷宮布局中的幾何信息。在MazeFloor游戲?qū)ο笊详P(guān)聯(lián)了MeshCollider和VRInteractiveItem,從而允許在VR場景中進(jìn)行交互。
MazeCourse關(guān)聯(lián)了一個(gè)MazeTargetSetting腳本,該腳本提供了OnTargetSet事件,并確定什么時(shí)候才能觸發(fā)這個(gè)事件。
image37.png
ExitArea腳本提供了游戲結(jié)束的事件OnGameComplete,確定了角色到達(dá)終點(diǎn)區(qū)域的行為。
Turret腳本控制炮塔的行為,Switch Button控制開關(guān)的行為。
System對象中主要是MazeGameController腳本。MazeGameController是整個(gè)場景的控制器,它負(fù)責(zé)整個(gè)游戲的流程、邏輯、不同游戲階段游戲?qū)ο蟮拈_關(guān)。
image38.png
Shooter180和Shooter360場景
在VR Samples包含了兩個(gè)射擊游戲,其中一個(gè)是回廊射擊游戲,玩家在180度視角的走廊中對潛在目標(biāo)射擊,場景如下左圖。另外還有一個(gè)競技場射擊游戲,玩家被類似X戰(zhàn)警場景的潛在目標(biāo)包圍,場景如下右圖。
image39.png
image40.png
第一人稱的胳膊和手槍是擺在相機(jī)前面的模型:
image41.png
System對象下面有4個(gè)子對象,用來控制游戲流程和邏輯,包括簡介、目標(biāo)生成、結(jié)尾。
image42.png
ShootingGalleryController腳本中的字段分別設(shè)置了游戲類型、理想目標(biāo)數(shù)量、生成目標(biāo)的概率、游戲時(shí)長、生成間隔、結(jié)尾延遲等等。
image43.png
ObjectPool腳本是對象池技術(shù),避免頻繁創(chuàng)建和刪除對象。
UIController封裝了游戲中UI的切換和顯示動(dòng)作。
ShooterWeapon是場景中的胳膊、武器和倒計(jì)時(shí)、數(shù)字界面的根節(jié)點(diǎn)。它是可以跟隨著相機(jī)轉(zhuǎn)動(dòng)的。
image44.png
ShootingGalleryGun腳本控制了槍的行為包括移動(dòng)和射擊。
image45.png
DefaultLineLength指的是,如果沒有目標(biāo)被擊中的話,線渲染器的長度;
Damping阻尼,設(shè)定的是這個(gè)物體跟隨相機(jī)的阻尼;
GunFlareVisibleSeconds設(shè)置的是槍開火之后的flare持續(xù)幾秒;
GunContainerSmoothing胳膊和槍的模型跟隨瞄準(zhǔn)星多快;
GunAudio設(shè)計(jì)的聲音;
ShootingGalleryController引用這個(gè)對象是為了在游戲沒有開始的時(shí)候,不讓槍射擊;
EyeRaycaster檢測槍正在瞄準(zhǔn)的東西;
VRInput告訴槍什么時(shí)候射擊;
CameraTransform為了獲取朝前的方向;
GunContainer要引用的胳膊和槍模型;
GunEnd槍的結(jié)束位置,用來作為線渲染的起點(diǎn);
GunFlare用來畫射擊軌跡;
Reticle瞄準(zhǔn)星的位置;
FlareParticles是個(gè)粒子系統(tǒng),槍開火的時(shí)候播放;
FlareMeshes槍開火的時(shí)候隨機(jī)激活其中之一;
UIMovement在VR場景中很有用,它使得UI元素始終保持在相機(jī)的前面一定距離,并跟隨相機(jī)旋轉(zhuǎn)??梢栽O(shè)置ui元素是否朝向相機(jī)、是否跟隨相機(jī)旋轉(zhuǎn)以及跟隨的速度等等。
image46.png
ShootingGalleryScore用來顯示玩家分?jǐn)?shù)。
場景中會(huì)生成許多目標(biāo)對象供玩家射擊,每個(gè)都是一個(gè)ShooterTarget對象,該對象主要是包括Mesh Collider,VRInteractiveItem,還有ShootingTarget腳本。ShootingTarget腳本中可以設(shè)置target對象消失的時(shí)長,被擊中后消失的時(shí)長,粉碎的對象,還有幾種聲效。
image47.png
總結(jié)
VR場景中基本的界面和交互元素在這些示例場景中均有涉及,能滿足絕大部分的需求,我們可以從中學(xué)習(xí)借鑒很多東西。作為官方給出的示例項(xiàng)目,該工程代碼組織和實(shí)現(xiàn)有很多值得學(xué)習(xí)的地方:
大量使用事件機(jī)制,設(shè)計(jì)松耦合;
大量使用Coroutine,實(shí)現(xiàn)場景進(jìn)度邏輯控制,特殊漸變效果;
對于場景中需要反復(fù)創(chuàng)建和銷毀的對象,采用了對象池技術(shù),避免了頻繁的內(nèi)存分配與釋放。