接下來的兩篇內(nèi)容济丘,是用Unity來做傳送門的效果。這里是用自己的方法來做的洽蛀,中途踩了許多坑不過最終還是實現(xiàn)了。下面是效果圖:
前半段內(nèi)容幾乎沒有代碼疟赊。主要是介紹一下實現(xiàn)的原理郊供。
先不說如何打開傳送門以及如何在傳送門中穿梭的問題,我們先來看看攝像機(jī)是怎樣將場景及主角從另一個角度渲染進(jìn)門里面的近哟。
在現(xiàn)實生活中驮审,如果把眼睛比作主攝像機(jī),人的眼睛是不能直接看見自己的側(cè)面的吉执,如果一定要看則會用另外的攝像頭從側(cè)面對自己進(jìn)行拍攝疯淫,再吧顯示器材放在視線范圍內(nèi),這樣就能實時的去觀察自己的側(cè)面了戳玫。同樣的道理熙掺,要達(dá)到上圖中的效果,一個攝像機(jī)是肯定不夠的咕宿,需要其它的攝像機(jī)來輔助币绩。
來看看我們的場景,場景很簡單府阀,就是一個墻面的地面組成的缆镣,當(dāng)傳送門打開時,就讓該處的墻暫時禁用:
讓主攝像機(jī)作為主角的眼睛始終跟隨主角移動试浙,如果主攝像機(jī)從傳送門看出去董瞻,門里其實是什么都沒有的:
那么這段“空白”就需要填補(bǔ),剛才我們提到了其它的攝像機(jī)田巴,就是用來填補(bǔ)這個空白的钠糊。
在效果展示中挟秤,主角從橙色門中由內(nèi)而外看出去,是和輔助攝像機(jī)從藍(lán)色門由外到內(nèi)看進(jìn)來的畫面是一樣的:
所以只要將輔助攝像機(jī)看到的畫面拿來填補(bǔ)主攝像機(jī)的空缺就可以了眠蚂,前提是主攝像機(jī)的渲染層級更高煞聪,我們對主攝像機(jī)的屬性進(jìn)行設(shè)置:
但是要真正的無縫銜接則對攝像機(jī)的位置關(guān)系有一定的要求,也就是說主攝像機(jī)與橙色門的位置關(guān)系同輔助攝像機(jī)與藍(lán)色門的位置關(guān)系必須是一致的:
要讓輔助攝像機(jī)的位置同步逝慧,我們可以創(chuàng)建一個空物體昔脯,取名叫Substitute,讓它成為橙色門的子物體笛臣,再創(chuàng)建一個輔助攝像機(jī)云稚,取名Camera_1,的視錐范圍要和主攝像機(jī)一樣沈堡,但渲染層級較低静陈,將Camera_1作為藍(lán)色門的子物體:
注意這里為了方便理解使用了中文,在實際開發(fā)中請避免使用中文命名诞丽。
通過代碼讓Substitute實時獲取主攝像機(jī)的世界坐標(biāo)的位置與旋轉(zhuǎn)鲸拥,再將本地坐標(biāo)賦給Camera_1,這樣輔助攝像機(jī)與藍(lán)色門的位置和主角與橙色門的位置就會保持同步:
[AppleScript]?純文本查看?復(fù)制代碼
1
2
3
4
//獲取主攝像機(jī)世界位置和旋轉(zhuǎn)
substitute.position=mainCamera.position;
substitute.rotation =mainCamera.rotation;[/color][/size][/font][/align][align=left][font=微軟雅黑][size=3][color=rgb(26,26,26)]//將本地位置和旋轉(zhuǎn)賦給輔助攝像機(jī)
camera_1.localPosition =substitute.localPosition;
camera_1.localRotation =substitute.localRotation;
同樣的方法僧免,再創(chuàng)建一個替身和輔助攝像機(jī)缩焦,也可以用來渲染藍(lán)色門內(nèi)的場景:
第三層空間的渲染
如果把主場景成為第一層空間渴语,傳送門看進(jìn)去的是第二層叭爱,那么上圖中紅框內(nèi)渲染的就是第三層愿吹。做到第三層空間的渲染,其實還是和渲染第二層一樣的原理浊洞,創(chuàng)建渲染第三層空間的輔助攝像機(jī)Camera_2:
Camera_1與橙色門的位置關(guān)系牵敷,就是Camera_2與藍(lán)色門的位置關(guān)系。
同樣顏色的線代表相同的位置關(guān)系
同樣的方法法希,將第三層的另一個攝像機(jī)也創(chuàng)建好枷餐,我們來看三層空間的效果展示:
這樣就保持著三層空間的渲染,當(dāng)然也可以再繼續(xù)往下發(fā)展苫亦,除第一層空間外尖淘,每層都需要兩個攝像機(jī),方法都是一樣著觉,越往下層的攝像機(jī)渲染層級越低村生,例如主攝像機(jī)的層級為0,那么第二層空間的兩個攝像機(jī)分別為-1和-2饼丘,第三層為-3和-4趁桃,且最低的攝像機(jī)的ClearFlags選項要設(shè)為Skybox或者SolidColor,其它所有攝像機(jī)都設(shè)置為Depth only。
而每個輔助攝像機(jī)都需要一個Substitute來幫助定位卫病,如果要渲染三層空間油啤,就一共需要四個Substitute(四個輔助攝像機(jī))。
本期文章內(nèi)容不多蟀苛,主要是介紹了傳送門的制作原理益咬。當(dāng)大家理解之后,在下一期中將會正式介紹物體在門中穿梭的實現(xiàn)方法帜平,屆時會少不了代碼幽告,并且同時會介紹過程中會遇到的許多坑。
這個項目一共只有5個腳本裆甩,拋開角色控制和人物動畫管理兩個腳本冗锁,主要講解剩下三個:
我們先將兩個傳送門放到場景中主攝像機(jī)照不到的地方:
接著上一篇,為了達(dá)到三層空間渲染效果嗤栓,一共創(chuàng)建四個輔助攝像機(jī)和四個Substitute冻河,把它們平均分配給兩個傳送門作為子物體:
創(chuàng)建一個空物體DoorManager,再創(chuàng)建一個腳本將它們都管理起來:
[AppleScript]?純文本查看?復(fù)制代碼
1
2
3
4
5
public classDoorManager :MonoBehaviour
{
????public Transform mainCamera; //主攝像機(jī)
????public Transform[] substitutes; //替身
????public Transform[] Cameras; //輔助攝像機(jī)
????public Door[] doors; //傳送門
編輯器里將它們拖進(jìn)去:
然后在一個方法里同步它們的位置和旋轉(zhuǎn):
[AppleScript]?純文本查看?復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
void SetSubstitutePos()//多層空間攝像機(jī)渲染
{
????//一層空間替身獲取主攝像機(jī)坐標(biāo)旋轉(zhuǎn)
????substitutes[0].position=substitutes[1].position=mainCamera.position;
????substitutes[0].rotation =substitutes[1].rotation =mainCamera.rotation;
????//二層空間攝像機(jī)獲取一層空間替身的本地坐標(biāo)旋轉(zhuǎn)
????Cameras[1].localPosition =substitutes[0].localPosition;
????Cameras[1].localRotation =substitutes[0].localRotation;
????Cameras[0].localPosition =substitutes[1].localPosition;
????Cameras[0].localRotation =substitutes[1].localRotation;
????//二層空間替身獲取二層空間攝像機(jī)的坐標(biāo)旋轉(zhuǎn)
????substitutes[2].position=Cameras[1].position;
????substitutes[2].rotation =Cameras[1].rotation;
????substitutes[3].position=Cameras[0].position;
????substitutes[3].rotation =Cameras[0].rotation;
????//三層空間攝像機(jī)獲取二層空間替身的本地坐標(biāo)旋轉(zhuǎn)
????Cameras[2].localPosition =substitutes[3].localPosition;
????Cameras[2].localRotation =substitutes[3].localRotation;
????Cameras[3].localPosition =substitutes[2].localPosition;
????Cameras[3].localRotation =substitutes[2].localRotation;
}
該方法放到LateUpdate里調(diào)用茉帅。
在圖中顏色相同線(除白線外)的長度(與傳送門的距離)相同叨叙,與傳送門的夾角也相同,白線表示在那個位置所擁有的物體堪澎。
開啟傳送門
我們的墻是一面面拼成的擂错,至于碰撞盒子為什么需要往外延伸,后面會講到它的作用全封。
開啟傳送門就是朝墻上發(fā)射子彈,如果碰到墻就讓該面墻的渲染禁用(看不見)桃犬,打開碰撞器的觸發(fā)功能(主角穿梭時不會被阻擋)刹悴,讓傳送門出現(xiàn)在該墻的位置,且兩扇傳送門的本地坐標(biāo)一個朝外攒暇,一個朝里土匀。
我們用一個計數(shù)器來記錄開門次數(shù),根據(jù)計數(shù)單雙來區(qū)別朝外和朝里形用,創(chuàng)建一個門的腳本Door就轧,在里面寫上開門的方法:
[AppleScript]?純文本查看?復(fù)制代碼
1
2
3
4
5
6
7
8
9
public classDoor :MonoBehaviour
{
????public float angle; //旋轉(zhuǎn)角度
????public void OpenDoor(Vector3pos,Quaternion rota)//獲取位置和旋轉(zhuǎn)打開傳送門
????{
????????//獲取新的位置和旋轉(zhuǎn)
????????transform.position=pos;
????????transform.rotation =rota;
????????transform.Rotate(0,angle,0);
????}
兩扇傳送門分別都掛上,并且其中一個的angle變量在編輯器里設(shè)為180田度,區(qū)分朝里和朝外妒御,然后在DoorManager腳本里調(diào)用:
[AppleScript]?純文本查看?復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Transform[] walls =newTransform[2]; //保存開門時被隱藏的墻
???int number=0; //計數(shù)器
???public void AddWall(Transform wall)//獲取當(dāng)前墻
???{
???????int i =number% 2;
???????if(walls !=null)//不為空則將之前隱藏的先顯示
???????????ShowWall(walls,true);
???????walls =wall;
???????if(number>0)
???????{
???????????ShowWall(walls,false); //隱藏當(dāng)前
???????????if(number==1)
???????????????ShowWall(walls[0],false);
???????}
???????OpenDoor(i); //打開傳送門
???????number++;
???}
???void ShowWall(Transform wall,bool b)//隱藏墻
???{
???????wall.GetComponent<BoxCollider>().isTrigger =!b; //開關(guān)觸發(fā)器
???????wall.GetComponent<SpriteRenderer>().enabled=b; //開關(guān)渲染器
???}
???void OpenDoor(int i)//打開傳送門
???{
???????doors.OpenDoor(walls.position,walls.rotation);
???????pm.startColor =i ==0? Color.red :Color.blue;
???}
AddWall方法會在子彈碰到墻時調(diào)用。
接下來我們做子彈的功能镇饺,開啟傳送門的子彈我們在場景中只有一個乎莉,當(dāng)它處于禁用狀態(tài)時才能開槍發(fā)射,然后子彈墻或飛出地圖一定距離時再禁用。
子彈我們用了一個移動時能產(chǎn)生拖尾的粒子效果惋啃,保留了碰撞器和剛體哼鬓,為它掛上一個腳本:
[AppleScript]?純文本查看?復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public classBullet :MonoBehaviour //子彈[/align]
[align=left]{[/align]
[align=left]public float speed;[/align]
[align=left]Rigidbody rig;[/align]
[align=left]DoorManager dm; //傳送門管理器[/align]
[align=left]Transform wall;[/align]
[align=left]bool open=true; //啟動傳送門也需要冷卻時間[/align]
[align=left]void Start()[/align]
[align=left]{[/align]
[align=left]rig =GetComponent<Rigidbody>();[/align]
[align=left]dm =FindObjectOfType<DoorManager>();[/align]
[align=left]}[/align]
[align=left]void Update()[/align]
[align=left]{[/align]
[align=left]rig.velocity =transform.forward *Time.deltaTime *speed; //前進(jìn)[/align]
[align=left]//飛出地圖一定距離自動禁用(地圖放在世界中心)[/align]
[align=left]if(Mathf.Abs(transform.position.z)>8|| Mathf.Abs(transform.position.x)>5)[/align]
[align=left]gameObject.SetActive(false); [/align]
[align=left]}[/align]
[align=left]void OnCollisionEnter(Collision other)//碰撞一次[/align]
[align=left]{[/align]
[align=left]if(other.collider.CompareTag("Wall"))[/align]
[align=left]{[/align]
[align=left]//撞到的墻不是剛才的墻,防止兩道門開在同一面墻上[/align]
[align=left]if(open&&wall !=other.transform)[/align]
[align=left]{[/align]
[align=left]open=false;[/align]
[align=left]wall =other.transform;[/align]
[align=left]dm.AddWall(other.transform);[/align]
[align=left]Invoke("ColdOpen",0.2f); //0.2秒后完成冷卻[/align]
[align=left]}[/align]
[align=left]}[/align]
[align=left]gameObject.SetActive(false); [/align]
[align=left]}[/align]
[align=left]void ColdOpen()[/align]
[align=left]{[/align]
[align=left]open=true;[/align]
[align=left]}[/align]
[align=left]
子彈的特效和準(zhǔn)心會根據(jù)DoorManager里的計數(shù)器單雙來決定當(dāng)前顏色:
傳送主角
我們結(jié)合旁觀者角度看看主角是怎么被傳送的:
簡單說边灭,就是判斷主角與某個傳送門之間的位置异希,達(dá)到一定位置條件就將另一個傳送門的位置賦給他,讓主角出現(xiàn)在另一個傳送門的位置绒瘦。
我們知道主角的位置和旋轉(zhuǎn)給了主攝像機(jī)称簿,而主攝像機(jī)的位置和旋轉(zhuǎn)又給了第一層空間的兩個substitute,而兩個substitute又分別是兩扇傳送門的子物體椭坚,所以只需要判斷substitute的本地坐標(biāo)就行了予跌,將傳送主角的方法寫在DoorManager腳本里:
[AppleScript]?純文本查看?復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
void DeliveryPlayer()//傳送主角
???{
???????if(number>=2)//有兩道門后可以執(zhí)行傳送
???????{
???????????DeliveryCondition(0,substitutes[0].localPosition.z >0);
???????????DeliveryCondition(1,substitutes[1].localPosition.z <0);
???????}
???}
???void DeliveryCondition(int i,bool b)//傳送主角條件
???{
???????int j =Mathf.Abs(i -1); //另一道門的索引
???????//判斷某個一層替身與父物體(傳送門)的位置關(guān)系
???????if(Mathf.Abs(substitutes.localPosition.x)<0.3f &&Mathf.Abs(substitutes.localPosition.y)<1&&b)
???????{
???????????//將主角傳送至另一道門位置
???????????player.position=Cameras[j].position;
???????????Quaternion r =Cameras[j].rotation;
???????????player.rotation =newQuaternion(player.rotation.x,r.y,player.rotation.z,r.w);
???????}
???}
把DeliveryPlayer方法也放在LateUpdate里實時監(jiān)測。
傳送子彈
傳送子彈使用觸發(fā)的方式善茎,當(dāng)傳送門被打開時券册,原本位置的墻會隱藏并開啟觸發(fā)器,我們之前使用了一個數(shù)組專門用來保存隱藏的墻垂涯,我們只需要在觸發(fā)時識別其中一個烁焙,然后立刻傳送到另一個的位置就行了,傳送子彈的方法我們寫在DoorManager里:
[AppleScript]?純文本查看?復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public void DeliveryBullet(Transform bullet,Transform wall)//傳送子彈
???{
???????bullet.parent =wall; //獲取該墻成為子彈父物體
???????//保存自身本地坐標(biāo)和旋轉(zhuǎn)
???????Vector3lp =bullet.localPosition;
???????Quaternion lr =bullet.localRotation;
???????//讓另一道門成為子彈父物體
???????if(wall ==walls[0])
???????????bullet.parent =walls[1];
???????else
???????????bullet.parent =walls[0];
???????//將剛才的本地坐標(biāo)和旋轉(zhuǎn)再賦予子彈
???????bullet.localPosition =newVector3(-lp.x,lp.y,-lp.z);
???????bullet.localRotation =lr;
???????bullet.Rotate(0,180,0,Space.World);
???}
然后在子彈的腳本里使用觸發(fā)方式調(diào)用:
[AppleScript]?純文本查看?復(fù)制代碼
1
2
3
4
5
void OnTriggerEnter(Collider other)//觸發(fā)一次
????{
????????wall =other.transform; //獲取被觸發(fā)的墻
????????if(wall !=transform.parent)//觸發(fā)的物體(墻)不是自己的父物體
????????????dm.DeliveryBullet(transform,wall); //傳送子彈
????}
傳送門動畫
暫停畫面會看到在同一個畫面中出現(xiàn)了兩個門耕赘,說明每個門都還有一個替身骄蝇,當(dāng)門的位置發(fā)生改變時,原來的門會變小操骡,然后以變大的方式呈現(xiàn)九火,原位置會用假門來替代,假門出現(xiàn)后會縮小并消失册招,在場景中導(dǎo)入兩個假門放在玩家看不見的地方岔激,然后在Door的腳本里添加動畫功能:
[AppleScript]?純文本查看?復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Vector3pos; //位置
?Vector3scale;//大小
?public float angle; //旋轉(zhuǎn)角度
?public Transform CopyDoor; //把假門拖進(jìn)去
?void Start()
?{
?????//記錄初始位置和大小
?????pos =transform.position;
?????scale =transform.localScale;
?}
?void Update()
?{
?????//位置發(fā)生改變
?????if(pos !=transform.position)
?????{
?????????//更新位置和旋轉(zhuǎn)信息
?????????pos =transform.position;
?????????transform.localScale =Vector3.zero;
?????}
?????//真門變大動畫
?????transform.localScale =Vector3.Lerp(transform.localScale,scale,Time.deltaTime *10);
?????if(CopyDoor.gameObject.activeInHierarchy)//如果假門被啟用調(diào)用展示動畫
?????????ShowPrefabDoor();
?}
?void DisplayDoor()//顯示假門
?{
?????//首先復(fù)制真門的位置旋轉(zhuǎn)尺寸,然后啟用
?????CopyDoor.position=pos;
?????CopyDoor.rotation =transform.rotation;
?????CopyDoor.localScale =scale;
?????CopyDoor.gameObject.SetActive(true);
?}
?void ShowPrefabDoor()//展示假門動畫
?{
?????//假門變小
?????CopyDoor.localScale =Vector3.Lerp(CopyDoor.localScale,Vector3.zero,Time.deltaTime *10);
?????if(CopyDoor.localScale.x <0.1f)//小到一定程度就禁用
?????????CopyDoor.gameObject.SetActive(false);
?}
我們來看一下慢動作效果:
接下來就說說會遇到的坑是掰,首先就是當(dāng)我們走到這個位置來的時候:
或者
這些都是第二層空間兩個攝像機(jī)的渲染層級沒設(shè)置好造成的虑鼎。
我們在DoorManager寫了一個方法,會根據(jù)兩個攝像機(jī)的位置實時得去調(diào)整他們的渲染層級:
[AppleScript]?純文本查看?復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
void SwitchCameraDepth()//切換二層空間攝像機(jī)層級
????{
????????//如果兩個一層替身本地高度差不多键痛,哪個替身離父物體Z軸距離近,一起的攝像機(jī)層級越低
????????if(Mathf.Abs(substitutes[0].localPosition.y -substitutes[1].localPosition.y)<0.1f)
????????{
????????????if(Mathf.Abs(substitutes[0].localPosition.z)<Mathf.Abs(substitutes[1].localPosition.z))
????????????????SetDepth(Cameras[0],Cameras[1]);
????????????else
????????????????SetDepth(Cameras[1],Cameras[0]);
????????}
????????else//如果高度相差很大炫彩,哪個替身與父物體Y軸距離小,一起的子物體攝像機(jī)層級越低
????????{
????????????if(Mathf.Abs(substitutes[0].localPosition.y)<Mathf.Abs(substitutes[1].localPosition.y))
????????????????SetDepth(Cameras[0],Cameras[1]);
????????????else
????????????????SetDepth(Cameras[1],Cameras[0]);
????????}
????}
????void SetDepth(Transform camera1,Transform camera2)//設(shè)置二層空間兩個攝像機(jī)渲染層級
????{
????????camera1.GetComponent<Camera>().depth =-3;
????????camera2.GetComponent<Camera>().depth =-2;
????}
然后放在LateUpdate里調(diào)用就可以解決剛才的問題了。
第二個坑是這里絮短,剛才我們提到為什么墻的碰撞盒子是這樣:
主要是為了防止這種情況的發(fā)生:
當(dāng)我們走到門口發(fā)射時江兢,原計劃要打到“前面”的墻,卻因為子彈產(chǎn)生的位置碰不到觸發(fā)器丁频,導(dǎo)致子彈無法被成功傳送划址,這樣就不能再“前方”墻上產(chǎn)生新的傳送門扔嵌。
解決辦法自然就是加厚碰撞器,讓主角走到很邊緣的位置也能保證子彈能被傳送夺颤。
22.png?(87.38 KB, 下載次數(shù): 0)
4?小時前?上傳
坑點主要就是這兩個痢缎。