[Unity C#教程] 游戏对象和脚本
文章轉(zhuǎn)載自:https://www.cnblogs.com/UnityYork/p/7704803.html
?
[Unity C#教程] 游戲?qū)ο蠛湍_本
博主最近在學(xué)習(xí)Unity,發(fā)現(xiàn)一個英文教程很好。這個教程通過一個個小項目講解了Unity方方面面,包括編輯器的使用,腳本的開發(fā),網(wǎng)格基礎(chǔ), 渲染和Shader等等,而且是由淺入深介紹的。這個教程是荷蘭的獨(dú)立軟件開發(fā)工程師Jasper Flick寫的,發(fā)表在了他自己的網(wǎng)站catlike coding。你可以在這個網(wǎng)站上看到這個教程和他的其他作品。
?
因為這個教程很不錯,所以我計劃把它翻譯下來,這樣對我的Unity技術(shù)和翻譯水平都會是一個提高的機(jī)會。 我會不定期更新,但是盡量一個月保證一篇到兩篇。我的Unity和英文翻譯水平都很有限,如果閱讀時發(fā)現(xiàn)什么錯誤,敬請指出。如果有什么關(guān)于文章的問題,也歡迎提問,大家一起討論。 另外需要說明的是,我已經(jīng)得到Jasper本人的許可翻譯和發(fā)表。?
?
這個教程的第一部分名為《游戲?qū)ο蠛湍_本》,是屬于第一章《基礎(chǔ)篇》的,它介紹如何用Unity來做一個三維的鐘表。前面先介紹搭建鐘表的模型如何制作,后面介紹腳本的開發(fā)。如果你按照他的教程做下來,你會得到如下的一個鐘表:
雖然這個項目看起來很簡單,但是對于初學(xué)者來說是一個很好的項目-- 因為你很快就可以做出一個確實能用的東西了。 而且教程內(nèi)一些小技巧我相信大牛也不一定都熟悉,比如表盤刻度的自動放置。所以好好讀讀這篇文章我相信會有所收獲。?
下面是這篇文章的正文翻譯:
游戲?qū)ο蠛湍_本 – 創(chuàng)建一個時鐘
原作者:Jasper Flick
原鏈接:http://catlikecoding.com/unity/tutorials/basics/game-objects-and-scripts/
翻譯者: York Zhang
翻譯者郵箱: york.zhang[at]outlook.com
用簡單的游戲?qū)ο髣?chuàng)建一個時鐘,
旋轉(zhuǎn)時鐘的指針來顯示時間,
讓指針動起來!
?
在這個教程里我們將創(chuàng)建一個簡單的時鐘,并編寫一個組件用來顯示當(dāng)前時間。你并不需要了解太多Unity編輯器。 如果你已經(jīng)用幾分鐘稍微熟悉了點(diǎn)Unity編輯器,知道了怎么在Scene(場景)中切換,就可以了。
這個教程是基于Unity 2017.1.0和以上版本的。
?
?是時候創(chuàng)建一個時鐘了。
打開Unity,然后創(chuàng)建一個新的3D工程。你不需要添加額外的Asset Packages,也不需要開啟Unity Analytics。如果你還沒有對Unity editor進(jìn)行界面的修改,你會得到如下的窗口布局:
默認(rèn)窗口布局
我用了一個不太一樣的布局,也就是”2 by 3”, 你可以從編輯器右上角的下拉菜單中選擇。我又進(jìn)一步對這個布局改了一下,將Project窗口修改成了”O(jiān)ne Column Layout”,這樣就更好的適應(yīng)了垂直方向。你可以通過修改工具條(toolbar)右上角的鎖圖標(biāo)附近的下拉菜單進(jìn)行修改。 另外,我關(guān)閉了位于Scene窗口的Gizmos下拉菜單里的Show Grid選項。
定制的“2 by 3”布局
為什么我的Game窗口有顯示有黑色的邊? 如果你用的是高分辨率顯示的話,會出現(xiàn)這個問題。 為了顯示整個Game窗口, 打開aspect-ratio下拉菜單,然后關(guān)閉Low Resolution Aspect Ratios 選項。 關(guān)閉狀態(tài)下的Low Resolution Aspect Ratios 選項。1.1?? 創(chuàng)建一個Game Object(游戲?qū)ο?
在默認(rèn)的場景里, 你能看到有兩個game objects。 他們是在Hierarchy窗口,你也能看到他們也在Scene窗口里。 第一個Game Object是Main Camera(主鏡頭),用來渲染場景。而Game窗口用這個鏡頭來渲染。第二個game object是Directional Light,用來照亮整個場景。
用GameObject-->Create Empty 選項來創(chuàng)建你的第一個game object。 你也可以通過右鍵菜單來在Hierachy里面創(chuàng)建。 這樣,在場景里就添加了一個新對象,然后你馬上就可以命名它。 因為我們要創(chuàng)建的是一個時鐘,我們就給它取名為Clock。
Hierarchy里的clock對象
?
Insepctor窗口含有g(shù)ame object的細(xì)節(jié)。 當(dāng)我們選擇clock這個game object的額時候, 它會顯示對象名字和一些設(shè)置在頂部。默認(rèn)情況下,這個game object是啟用狀態(tài)的,非靜態(tài)的,不帶標(biāo)簽(tag)的,而且它歸屬于一個默認(rèn)的層(layer)。 這些設(shè)置是對我們來說是可以的。 在這些下面,你會看到這個game object的所有組件。一般來說總會有一個Transform組件,而我們clock這個game object 唯一有的組件就是它。
?
選擇clock之后的Inspector窗口
Transform組件包含game objec在三位空間中的位置(position),旋轉(zhuǎn)(rotation)和大小(scale)。你需要確保前兩者的值都是0,而第三者的值都是1。
二維的game object是什么情況呢? 在二維而不是三維的情況下, 你可以忽略其中一個維度。一般來說,像UI元素這樣的2維對象,都會有一個Rect Transform,這是一個比較特殊的Transform組件。1.2?? 創(chuàng)建表盤
?
雖然我們有了一個clock對象,但是我們在場景中什么都沒有看到。 我們需要添加三維模型,它才能渲染出來。 Unity自帶一些基礎(chǔ)的對象,我們可以用它們來做簡單的時鐘。我們先通過GameObject –> 3D object--> Cylinder 來添加一個圓柱體。 讓它和我們的clock對象有一樣的Transform值
一個圓柱體的Game Object
和空的game object不同的是,這個新創(chuàng)建的game object多出三個組件。首先,它有一個Mesh Filder。它只是包含了對內(nèi)置的圓柱體網(wǎng)格的參照。第二個是一個Capsule Collider(膠囊碰撞體), 這是一個描述三維物理的組件。第三個是Mesh Renderer(網(wǎng)格渲染器)。這個組件用來確保物體的網(wǎng)格會被渲染,他也控制了用來渲染的材質(zhì)。你如果不對它進(jìn)行更改,它會使用Default-Material(默認(rèn)材質(zhì))。你在inspector里的組件列表里也能看到這個材質(zhì)。
雖然說這個對象用來表示一個圓柱體,但是它還有一個膠囊碰撞體組件,因為Unity并不含有原始的圓柱體碰撞體。我們不需要這個膠囊碰撞提組件,所以我們可以刪除它。 如果你想在你的表上用到物理特性,你最好不要用Mesh Collider組件。 你可通過上方右邊的齒輪圖標(biāo)旁邊的下拉菜單來刪除組件。
?
不再有碰撞體
為了將這個圓柱體變成表盤,我們得壓扁它。你需要減少Scale里Y的值到0.1. 因為圓柱體網(wǎng)格是兩個單元高,它的有效高度變成了0.2個單元。 讓我們做一個大時鐘,將Scale里X和Z的值改為10。
大小變化后的圓柱體
?因為這個圓柱體表示表盤,你需要將圓柱體的名字更改為Face。它是時鐘的一部分,所以讓其成為Clock的子對象。要做到這點(diǎn),你需要在Hierarchy里將它拖到clock上面。
表盤子對象
子對象是受制于它的父對象的,意思是說當(dāng)Clock改變了位置,表盤也會變。就好像他們是一體的。 旋轉(zhuǎn)和大小也一樣。 你可以用這個方式去制作更復(fù)雜的關(guān)系。
1.3?? 創(chuàng)建時鐘的外圈
時鐘的表盤上一般會有標(biāo)記來幫助指出具體什么時間。也就是說表的外圍。 讓我們用方塊來指一個12小時的時鐘的時間。
?
通過GameObject-->3D Object-->Cube來添加一個立方體(Cube)。將它的大小調(diào)整為(0.5,0.2,1),然后他就變成了一個又細(xì)又扁的長形方塊。 現(xiàn)在它就在表盤上,那么我們需要將其位置(position)修改為(0,0.2,4)。這樣他就會位于表盤的最頂端,表示12點(diǎn)的刻度。讓我們將它命名為Hour Indicator(小時刻度)。
12小時的刻度
因為這個小時刻度的顏色和表盤一樣,所以不太容易看得見它。讓我們通過Assets-->Create-->Material, 或者在Project窗口點(diǎn)擊右鍵菜單來讓我創(chuàng)建另外一個材質(zhì)。你會發(fā)現(xiàn)這個材質(zhì)和之前的默認(rèn)材質(zhì)是一樣的。將Albedo值改成深一點(diǎn)的顏色,比如Red紅,Green綠和Blue藍(lán)都為73。這樣我們就得到一個深灰色的材質(zhì)。給它一個合適的名字,比如Clock Dark。
?
黑色材質(zhì)asset和顏色的彈出窗口。
?
什么是Albedo?Albedo是一個拉丁字,用來表示白色。Unity用其來表示材質(zhì)的顏色。
讓我們來將這個材質(zhì)添加到小時刻度上。你可以將材質(zhì)拽到場景里或者Hierachy窗口里的對象上, 也可以將其拽到inspector窗口的底部或者改變mesh render的材質(zhì)數(shù)組,將其設(shè)為第0個元素。
黑色小時刻度
我們的刻度已經(jīng)正確的放在了12點(diǎn)的位置,但是1點(diǎn)位置怎么辦呢?因為一天有12個小時,一個表盤的一圈是360都,所以我們需要將刻度沿著y軸旋轉(zhuǎn)30。 來讓我們試試。
?
位置錯誤的旋轉(zhuǎn)的小時刻度
?
盡管我們得到了正確的角度,刻度依然是12點(diǎn)的位置。 這是因為一個對象的旋轉(zhuǎn)是相對于它本身所在位置的。
我們需要將刻度沿著表盤的邊緣移動,將其調(diào)整為1點(diǎn)。我們可以不自己去搞清楚這個位置,而用對象的Hierarchy來幫我們做。首先將刻度的旋轉(zhuǎn)都設(shè)為0然后新建一個空對象,這個對象的位置和旋轉(zhuǎn)都是0,大小都是1. 將刻度拖到它下面。
臨時父對象
?
?
現(xiàn)在,將這個父對象的旋轉(zhuǎn)改為30度。 這樣刻度也會轉(zhuǎn)了,繞著其父對象的原點(diǎn),最后落在了我們想讓它在的位置。
位置正確的小時刻度
用Ctrl/Command+D鍵,或者你也可以用這個對象的右鍵菜單來反復(fù)復(fù)制這個臨時的父對象。每個父對象在Y的旋轉(zhuǎn)值上增加30度。不斷重復(fù)這個動作知道你得到每個小時的刻度。
12小時的刻度
現(xiàn)在我們不再需要這些臨時的父對象了。 在Hierarchy里選擇其中一個小時刻度, 將其拽到clock對象里。之后它就成為了clock的一個子對象。 這時, Unity改變了刻度的transformation,所以它的位置和旋轉(zhuǎn)并沒有在Word Space(世界空間)里改變。將所有的12個刻度都拖進(jìn)clock對象里,然后刪除所有的臨時父對象。如果你想做的快點(diǎn),你可以用ctrl或者commend按鍵來進(jìn)行對對象的多重選擇。
外圈子對象
我看到一些值是90.00001.這是怎么回事?當(dāng)位置(position), 旋轉(zhuǎn)(rotation)和大小(scale)的值是浮點(diǎn)數(shù)的時候,這個問題可能會出現(xiàn)。 這些數(shù)字的不是非常非常準(zhǔn)確,所以導(dǎo)致了微小的誤差。你不需要擔(dān)心這個0.00001的誤差因為這幾乎無法察覺到。
1.4? 創(chuàng)建指針
我們可以用相同的方法來構(gòu)建指針。 創(chuàng)建另一個立方體(cube)并將其命名為Arm,然后將同樣的黑色材質(zhì)給他。 將它的大小設(shè)置為(0.3,0.2,2.5),這樣它會比刻度更長更細(xì)一些。 將位置(position)設(shè)為(0,0.2,0.75),這樣它就會位于表盤之上,并且指向12點(diǎn)的位置你會看到有一部分會在反向一點(diǎn)點(diǎn),這讓這個指針旋轉(zhuǎn)時看起來會比較平衡一點(diǎn)。
?
時針
光照的圖標(biāo)去哪了? 我把光挪開了,這樣它就不會弄亂場景。 因為其是一個平行光(directional light),它的位置其實并不重要。為了讓指針繞著時鐘的中心旋轉(zhuǎn), 就像我們處理小時刻度那樣, 需要創(chuàng)建一個父對象給它。 我們依然需要將這個父對象的位置默認(rèn)值和旋轉(zhuǎn)默認(rèn)值設(shè)為0,大小設(shè)為1. 因為我們之后需要旋轉(zhuǎn)指針,所以將這個父對象作為clock的子對象,然后命名為Hours Arm。這樣, Arm就成為clock了一個的“孫對象”。
?
Clock 的hierarchy里面的三個表針
?
反復(fù)復(fù)制Hours Arm兩次來創(chuàng)建分針(Minutes Arm)和秒針(Seconds Arm)。 分針應(yīng)該逼時針更長更細(xì),因此將Arm子對象的大小設(shè)為(0.2,0.15,4),位置設(shè)為(0,0.375,1)。這樣分針就會在時針上面了。
對于秒針,將其大小設(shè)為(0.1,0.1,5),位置設(shè)為(0,0.5,1.25).為了進(jìn)一步區(qū)分,我創(chuàng)建了一個名為Clock Red的材質(zhì),這個材質(zhì)的albedo的RGB值為(197,0,0)。之后將這個材質(zhì)添加到了Arm子對象。
所有三個指針
?
我們的時鐘現(xiàn)在做好了。如果你還沒保存場景,現(xiàn)在保存一下吧。它會作為一個asset保存在工程里。
?
保存的場景
如果你卡在哪里,或者需要比較你做的項目和我做的項目,或者想完全跳過構(gòu)建這個clock的過程,你可以下載一個包含上述所有工作的包。通過Asset-->Import Package-->Custom Package, 你可以將這些包導(dǎo)入到一個Unity工程里。你也可以將其拽入Unity窗口或者在文件瀏覽器里雙擊這個包來導(dǎo)入。
?
2 鐘表動畫的制作
我們的鐘表目前還沒有時間的顯示。它只是Hierarchy的一個對象,是Unity渲染的一堆網(wǎng)格 -- 僅此而已。假如有個默認(rèn)的鐘表組件,我們就可以讓它表示時間了。但是并沒有,所以我們需要自己做一個了。 組件是通過腳本(script)來實現(xiàn)的。 通過Assets-->Create-->C# Script來新建一個新的腳本資源(script asset)并將其命名為Clock。
Clock 腳本asset
?
?
腳本被選擇的時候,Inspector將顯示它的內(nèi)容和一個用來在代碼編輯器里打開這個文件的按鍵。你也可以通過雙擊這個腳本來打開編輯器。 腳本文件將包含默認(rèn)的代碼模板,看起來是這個樣子的:
using System.Collections; using System.Collections.Generic; using UnityEngine;public class Clock : MonoBehaviour {
</span><span style="color: #008000">//</span><span style="color: #008000"> Use this for initialization</span> <span style="color: #0000ff">void</span><span style="color: #000000"> Start () {}</span><span style="color: #008000">//</span><span style="color: #008000"> Update is called once per frame</span> <span style="color: #0000ff">void</span><span style="color: #000000"> Update () {}}
這就是C#代碼。這是一種用來寫Unity腳本的編程語言。 為了了解這些代碼是如何工作的,先讓我們?nèi)堰@些代碼刪掉,從頭開始。
Javascript語言怎么樣? Unity也支持另一種編程語言,叫做Javascript,而實際上它應(yīng)該叫做UnityScript。 Unity 2017.1.0 仍然支持它,但是用來創(chuàng)建JavaScript的菜單項會在2017.2.0版本里移除,對這個語言的支持可能會在這之后徹底結(jié)束。?
2.1 定義一個組件類型
?
一個空文件不是一個有效的腳本。 它必須包含我們時鐘的定義。 我們不會給一個組件只定義一個實例。然而,我們會給它定義一個類(class)或者類型(type),也就是Clock。 一旦做好了,我們就可以通過它建立很多這樣的組件。
在C#里來定義Clock,我們首先我們先定義一個Class,然后是它的名字。 在下面的代碼片段里,修改的代碼會有黃色的背景。因為我們最先開始是創(chuàng)建一個空文件,所以它應(yīng)該正確的寫上class Clock,其他都沒有。 當(dāng)然,如果你在中間添加空格或者添加新的一行都可以的。
class Clock 一個Class(類)到底是什么呢? 你可以將一個Class想象成一個藍(lán)圖(blueprint),它可以用來在計算機(jī)的內(nèi)存里創(chuàng)造對象。 這個藍(lán)圖定義了這些對象應(yīng)該含有的數(shù)據(jù)和功能。Class也可以定義它自己的數(shù)據(jù)和功能,而不是其對象的。一般來說,全局可用的功能會用這些數(shù)據(jù)和功能。
因為我們不想限制代碼來訪問我們的Clock,所以我們需要給它添加Public 訪問修飾符。
什么是類的默認(rèn)的訪問修飾符?當(dāng)沒有訪問修飾符的時候,就相當(dāng)于internal class Clock。 這樣就將訪問區(qū)域限制在同一個Assembly里,一旦你將代碼打包成多個DLL文件的時候就會有關(guān)系了。為了確保這個class總好使,一般狀況下將其標(biāo)記為public
現(xiàn)在這樣我們并沒有一個有效的C#語法。 我們指出來我們要定義一個類型,所以我們必須實際定義它是什么樣子的。 這要通過在上述聲明之后一些代碼段來實現(xiàn)。 這些代碼段的是由大括號括起來的。我們先不在這個括號里什么都不寫,只用{}。
public class Clock {}現(xiàn)在我們的代碼是有效的了。 保存文件,然后回到Unity編輯器。這時候Unity編輯器就會檢測到我們的腳本已經(jīng)改變并再次編譯了這段代碼。 然后,選擇我們的代碼,Inspector會通知我們這個asset并沒有包含MonoBehaviour腳本。
非組件的腳本
這個信息的意思是說我們不能用這個腳本去創(chuàng)建一個Unity組件。目前,我們的Clock只是定義了一個通用的C#對象類型。 Unity只能用MonoBehaviour的子類型來創(chuàng)建組件。
?
Mono-behivour是什么意思呢?Behavour的意思是說我們可以編寫我們自己的組件來添加定制化的行為給Game Object。 比較奇怪的一點(diǎn)是它實際上是英式的拼寫方式。(美式單詞為Behavior)。
Mono的意思是定制化代碼添加給Unity的方式。 這個次曾經(jīng)被用在Mono項目,Mono項目實際上是.NET Framework在多平臺上的部署。 因此,MonoBehaviour實際上是一個老名字, 我們用它是為了向前兼容。
為了將Clock轉(zhuǎn)換成Monobehavour的子類型, 我們改變我們的類型聲明,這樣就擴(kuò)展了那個類型。 我們用冒號來表示。 這讓Clock繼承了MonoBehaviour的所有功能。
public class Clock : MonoBehaviour {}然而,編譯之后,這會導(dǎo)致一個錯誤。 編譯器會抱怨說它不能找到MonoBehaviour類型。 這是因為MonoBehaviour類型是包含在一個namespace下面的,這個namespace就是UnityEngine。 要訪問它,我們需要用它的全名,即Unity.MonoBehaviour.?
public class Clock : UnityEngine.MonoBehaviour {} 什么是namespace(命名空間)? Namespace就像一個網(wǎng)站的域,但是是用在代碼里的。 就像域可以有子域, namespace也可以有subnamespaces(子命名空間)。 一個很大的區(qū)別是命名空間是反著寫的。所以不會寫成forum.unity3d.com,而是寫成com.unity3d.forum.因為代碼來自于Unity,你不需要去線上單獨(dú)拿到它。Namespace是用來組織代碼和防止命名沖突的。?
因為每次都加上UnityEngine的前綴實在是不方便,當(dāng)我們完全不提它的時候,我們可以讓編譯器去查找這個namespace。這是通過在文件頂部添加using UnityEngine; 來實現(xiàn)的。 注意分號是命令最后一定要添加的。
using UnityEngine;public class Clock : MonoBehaviour {}
現(xiàn)在我們能夠添加到我們的clock game object到Unity了。 你可以將這個script直接拽到對象上,也可以通過對象的inspector下面的Add Component按鈕來添加。
Clock對象和我們創(chuàng)建的組件
現(xiàn)在用我們的Clock類作為模板的一個C#對象實例已經(jīng)創(chuàng)建了。 它被添加在了Clock game object的組件列表里。
2.2 處理指針
?
為了旋轉(zhuǎn)這些指針,Clock對象需要知道他們。讓我們從時針開始 就像所有的game object一樣, 它能夠通過改變transform里旋轉(zhuǎn)的值來旋轉(zhuǎn)。 所以我們需要將是真的信息傳給Clock。 這可以通過在代碼段里添加數(shù)據(jù)字段來實現(xiàn),這些數(shù)據(jù)字段是由字段名和之后的分號定義的。
Hours transform是一個合適的名字。但是名字必須是單字。最方便的方式是讓第一個詞的字母小寫其他的詞的首字母大寫,然后將他們寫在一起。所以就成了hoursTransform。
public class Clock : MonoBehaviour { <span style="background-color: #ffff00">hoursTransform;</span>}
Using那行去哪了? 它還在那,只是我們沒寫出來它。 代碼片段值包含足夠的已經(jīng)存在的代碼,這樣你就知道改變的上下文環(huán)境了
我們還需要定義字段的類型,現(xiàn)在這種情況下就是UnityEngine.Transform。 要把它放在名字的前面。
Transform hoursTransform;我們的class現(xiàn)在定義了一個字段,它能包含另一個Transform對象的引用。 我們需要保證它包含時針上transform組件的引用。
字段默認(rèn)是private的,也就是說他們只能被Clock內(nèi)部的代碼訪問。但是clock類不能被我們的Unity場景訪問。 為了讓任何人都能訪問修改,我們可以將這個字段標(biāo)記為public。
public Transform hoursTransform;?
Public字段不是不好的形式么?一般來說,編程者的大多數(shù)的共識是避免創(chuàng)建public字段。但是,把代碼和Unity聯(lián)系起來,public字段又是需要的。當(dāng)然你可以通過某種方式繞過它,但是這樣就讓代碼不是那么直觀了。
當(dāng)字段是public的時候,inspector窗口就會顯示它。這是因為inspector會自動讓所有的public字段組件可以編輯。
時針的transorm字段
?
為了建立它們的聯(lián)系,將Hours Arm從Hierarchy拽到Hours Transform 字段。 或者,點(diǎn)右側(cè)的小圓點(diǎn)來搜索Hours Arm。
已經(jīng)連接上的Hours Transform
?
當(dāng)Hours Arm被放置在里面后,Unity編輯器就會抓取它的transform組件,并在我們的field里引用它。
2.3 關(guān)于時針,分針和秒針
?
我們需要對時針和秒針做一樣的操作。 所以要添加兩個不同名字的字段到Clock。
public Transform hoursTransform; public Transform minutesTransform; public Transform secondsTransform;因為他們是有相同的訪問修飾符, 你可以考慮讓這些字段的聲明更加簡潔。他們可以被合并成一個前面是訪問修飾符和類型的用逗號分隔開的列表:
public Transform hoursTransform, minutesTransform, secondsTransform; // public Transform minutesTransform; // public Transform secondsTransform; “//”符號是干什么的? 雙斜線(//)用來表示注釋。直到這行結(jié)束,他們后面的所有文字都會被編譯器忽略。 他們后面的文字一般用來在需要的時候解釋代碼。 我也會用來標(biāo)注哪些代碼被刪除了。將另外兩個指針也掛在編輯器上:
所有的指針都連接上了
2.4?關(guān)于時間
現(xiàn)在我們在Clock里完成了指針,下一步就是搞清楚現(xiàn)在的時間。為此,我們需要讓Clock去執(zhí)行一些代碼。這需要通過添加代碼段到Class,也就是方法(Method)。這些代碼段的前綴是它的名字,取名字的慣例是首字母大寫。讓我們給其取名為Awake,就是說這些代碼會在component剛蘇醒的時候被執(zhí)行。
public class Clock : MonoBehaviour { </span><span style="color: #0000ff">public</span><span style="color: #000000"> Transform hoursTransform, minutesTransform, secondsTransform;<span style="background-color: #ffff00">Awake {}</span>}
Method有些像數(shù)學(xué)的函數(shù)。比如f(x) = 2x +3。 這個函數(shù)拿來一個數(shù),乘以2,加上3. 它拿來的是一個數(shù),結(jié)果也是一個數(shù)。在方法里,更像f(p) = c, 這里p表示輸入的參數(shù),c表示代碼執(zhí)行。 那么你自然會問,那么這個功能執(zhí)行的結(jié)果是什么呢? 這個我們之后會細(xì)講?,F(xiàn)在這種情況下,我們只是想執(zhí)行一些代碼,而不提供一個結(jié)果的值。 換句話說,這個方法的結(jié)果是空(void)的。因此我們通過用void前綴來指出結(jié)果是空的。
void Awake {}我們也不需要任何輸入?yún)?shù)。但是我們還是需要定義方法的參數(shù),這些參數(shù)要在圓括號里用都好分隔開。不過我們現(xiàn)在的情況下,它就是空的而已。
void Awake () {}現(xiàn)在,我們有了一個有效的方法,只不過它現(xiàn)在還沒做任何事。 就像Unity檢測了到我們的字段, 它也檢測到了Awake方法。 當(dāng)一個組件含有Awake,當(dāng)這個組件蘇醒時,Unity會調(diào)用哪個方法,一般發(fā)生在它被創(chuàng)建或者加載時。
Awake方法難道不應(yīng)該是public的么?Awake和一系列其的方法在Unity是特殊的。 Unity會在合適的時候?qū)ふ宜鼈兒驼{(diào)用它們,不管我們?nèi)绾温暶魉鼈儭?我們不應(yīng)該讓這些方法public, 因為它們不該被除了Unity引擎意外其他的任何類調(diào)用
為了檢測這個方法是否工作,讓我們來創(chuàng)建一個調(diào)試信息。UnityEngine.Debug類包含了一個公眾可用的變量叫做Log,它可以用來做這件事。 我們傳遞給它一個簡單的字符串文本來打印。字符串要卸載兩個引號中間。再次提醒,分號是結(jié)束這個表達(dá)式的必要符號。
void Awake () {Debug.Log("Test");}在Unity編輯器里運(yùn)行它,你將會在編輯器狀態(tài)欄上會顯示這個測試的字符串。 如果你在Window-->Console里打開Console(控制臺)窗口,你也會看到這些字符串。 Console還會提供一些額外的信息,比如當(dāng)你選擇一段文字就會看到哪個代碼生成的這些消息。
現(xiàn)在我們知道我們的方法好使,然后我們就要搞清楚當(dāng)它運(yùn)行的時候的時間。 UnityEngine命名空間包含了一個Time類,這個類包含了一個time屬性。好像當(dāng)然我們應(yīng)該用它,所以讓我們用log來顯示它。
void Awake () {Debug.Log(Time.time);} 什么是屬性(property)? 屬性是一個偽裝成字段的方法。他可能是只讀或者只寫的。他的名字按照慣例一般是首字母大寫, 但是Unity往往不遵守這個慣例。?
我們發(fā)現(xiàn)log的值總是0.這是因為Time.time實際上是游戲運(yùn)行后的時間。 因為我們的Awake方法是立即被調(diào)用的,沒有時間流逝,所以這個Time.time并沒有告訴我們真實的時間。
為了訪問我們電腦的系統(tǒng)時間,我們需要用到DateTime結(jié)構(gòu)體(struct)。這并不是Unity類型。他存在于System命名空間里。 他是.NET Framework里的核心功能的一部分,Unity可以用它來支持腳本的開發(fā)。
什么是結(jié)構(gòu)體(struct)?結(jié)構(gòu)體就像類,也是一個藍(lán)圖。 區(qū)別是,他創(chuàng)建的都是簡單的數(shù)值,比如整形數(shù)字或者顏色,而不是一個對象。 你可以像定義類那樣定義結(jié)構(gòu)體,知識要用struct而非class關(guān)鍵字。
DateTime有一個公眾可訪問的屬性Now。他產(chǎn)生了DateTime的一個值,這個值就是現(xiàn)在的系統(tǒng)日期和時間。 讓我們輸出它看看。
using System; using UnityEngine;public class Clock : MonoBehaviour {
</span><span style="color: #0000ff">public</span><span style="color: #000000"> Transform hoursTransform, minutesTransform, secondsTransform;</span><span style="color: #0000ff">void</span><span style="color: #000000"> Awake () {Debug.Log(<span style="background-color: #ffff00">DateTime.Now</span>); }}
現(xiàn)在我們每次進(jìn)入運(yùn)行模式之后都會得到一個時間間隔。
2.5?旋轉(zhuǎn)指針
下一步就是根據(jù)當(dāng)前時間來旋轉(zhuǎn)指針了。 讓我們先從小時開始。 DateTime有一個Hour的屬性表示小時。在目前的時間段調(diào)用它你就會得到一天中的小時時間。
void Awake () {Debug.Log(DateTime.Now.Hour);}我們可以用他來建立一個旋轉(zhuǎn)機(jī)制。旋轉(zhuǎn)在Unity被存儲為四元數(shù)(quaternions)。我們可以通過一個公眾可訪問的Quaternion.Eular方法來創(chuàng)建一個旋轉(zhuǎn)。 他包含X,Y,Z三個軸的角度作為參數(shù),然后輸出為一個合適的四元數(shù)。
void Awake () { // Debug.Log(DateTime.Now.Hour);Quaternion.Euler(0, DateTime.Now.Hour, 0);} 什么是四元數(shù)(quaternion)? 四元數(shù)是復(fù)雜的數(shù)學(xué)概念,用來表示三維空間的旋轉(zhuǎn)。雖然比起三維空間的維度它更難以理解,但是它有一些很有用的特性。比如,它不會導(dǎo)致萬向節(jié)死鎖(gimbal lock)。UnityEngine.Quaternion是被用作簡單的數(shù)值。它是結(jié)構(gòu)體,而不是類。
?
所有的三個參數(shù)都是真實的數(shù)值,在C#里都被表示為浮點(diǎn)數(shù)值。 為了明確的聲明我們提供給方法給這些數(shù)字,讓我們在所有的0后面加上f后綴。
?
Quaternion.Euler(0f, DateTime.Now.Hour, 0f);我們的鐘表有12個小時的刻度,所以每個刻度之間是30度。 為了讓旋轉(zhuǎn)匹配上,我們要將小時乘以30。 乘法是用星號(*)來運(yùn)算的。
Quaternion.Euler(0f, DateTime.Now.Hour * 30f, 0f);為了說清楚我們是將小時轉(zhuǎn)換成度數(shù)的,也為了以后方便,我們可以定義一個合適名字的字段。 因為它是浮點(diǎn)類型的,它的類型就是float。因為我們已經(jīng)知道數(shù)字了,所以我們可以直接在聲明的時候就立即賦值。
float degreesPerHour = 30f; </span></span><span style="color: #0000ff">public</span><span style="color: #000000"> Transform hoursTransform, minutesTransform, secondsTransform;</span><span style="color: #0000ff">void</span><span style="color: #000000"> Awake () {Quaternion.Euler(0f, DateTime.Now.Hour </span>*<span style="color: #000000"><span style="background-color: #ffff00"> degreesPerHour</span>, 0f); }</span></pre>每小時旋轉(zhuǎn)的角度應(yīng)該是不變的。我們可以通過在聲明的時候添加const前綴來強(qiáng)調(diào)這點(diǎn)。這樣degreesPerHour就變成了一個常量。
const float degreesPerHour = 30f; 常量(constant)有什么特別的?Const關(guān)鍵字指出這個值永遠(yuǎn)都不會變,也不需要成為一個字段。 他的值會被編譯的時候計算,也會被常量取代。 常量只可以用在原始類型,比如數(shù)字。
目前為止,我們經(jīng)由個旋轉(zhuǎn),但是我們還沒對它做什么,他這樣就不起作用。 為了將其應(yīng)用到時針上, 我們要將其賦值時針的Transform里的localRotation屬性。
void Awake () {hoursTransform.localRotation =Quaternion.Euler(0f, DateTime.Now.Hour * degreesPerHour, 0f);}4點(diǎn)時的時針指向
“l(fā)ocal”在旋轉(zhuǎn)里是什么意思?localRotation用來表示transform組件的實際旋轉(zhuǎn),獨(dú)立于他的父對象。換句話說, 這是對象本地空間的旋轉(zhuǎn)。 這就是inspector里transform組件里顯示的值。如果我們旋轉(zhuǎn)clock的根對象,你能想到,他的指針會繞著這個根對象旋轉(zhuǎn)。
其實還有一個rotation屬性。 他用來表示transform組件在世界空間的的最終旋轉(zhuǎn),而且要把它父對象的旋轉(zhuǎn)考慮進(jìn)去。 假如我們用這個屬性,當(dāng)我們旋轉(zhuǎn)時鐘的時候,指針的位置不會調(diào)整, 因為他的旋轉(zhuǎn)會被補(bǔ)償。
const floatdegreesPerHour = 30f,degreesPerMinute = 6f,degreesPerSecond = 6f; </span></span><span style="color: #0000ff">public</span><span style="color: #000000"> Transform hoursTransform, minutesTransform, secondsTransform;</span><span style="color: #0000ff">void</span><span style="color: #000000"> Awake () {hoursTransform.localRotation </span>=<span style="color: #000000">Quaternion.Euler(0f, DateTime.Now.Hour </span>*<span style="color: #000000"> degreesPerHour, 0f);<span style="background-color: #ffff00">minutesTransform.localRotation </span></span><span style="background-color: #ffff00">=<span style="color: #000000">Quaternion.Euler(0f, DateTime.Now.Minute </span>*<span style="color: #000000"> degreesPerMinute, 0f);secondsTransform.localRotation </span>=<span style="color: #000000">Quaternion.Euler(0f, DateTime.Now.Second </span>*</span><span style="color: #000000"><span style="background-color: #ffff00"> degreesPerSecond, 0f);</span> }</span></pre>?
鐘表顯示為16:29:06
?
我們用DateTime.Now三次來獲取小時,分鐘和秒。每次我們都進(jìn)入到屬性里,做一些工作,這理論上會導(dǎo)致不同的時間。 為了防止這種情況的發(fā)生,我們需要確保時間只獲取一次。 我們可以先在方法里聲明一個變量,把整個時間都賦值給它,然后使用這個變量。
什么是變量? 變量想字段,只不過它只存在方法被執(zhí)行的時候。 他屬于方法而不是類。 void Awake () {DateTime time = DateTime.Now;hoursTransform.localRotation =Quaternion.Euler(0f, time.Hour * degreesPerHour, 0f);minutesTransform.localRotation =Quaternion.Euler(0f, time.Minute * degreesPerMinute, 0f);secondsTransform.localRotation =Quaternion.Euler(0f, time.Second * degreesPerSecond, 0f);}2.6?表針動畫
?
當(dāng)你進(jìn)入運(yùn)行模式的時候,你會看到當(dāng)前時間。 但是時鐘是靜止的。 為了保持時鐘的時間和我們的目前時間是一致的,修改Awake方法為Update。 在運(yùn)行模式中, 這個方法會被Unity在每幀都調(diào)用。
void Update () {DateTime time = DateTime.Now;hoursTransform.localRotation =Quaternion.Euler(0f, time.Hour * degreesPerHour, 0f);minutesTransform.localRotation =Quaternion.Euler(0f, time.Minute * degreesPerMinute, 0f);secondsTransform.localRotation =Quaternion.Euler(0f, time.Second * degreesPerSecond, 0f);}在組件名字的前面,現(xiàn)在我們的組件還獲得了一個開關(guān)。這個開關(guān)允許我們關(guān)閉這個組件,如果關(guān)閉了Unity就不會調(diào)用Update方法了。
2.7 連續(xù)滾動
我們時鐘的指針準(zhǔn)確的指向了小時,分鐘和秒,但是它更像一個數(shù)字時鐘,這個數(shù)字時鐘是不連續(xù)的,卻是有指針的。 很多時鐘都會有慢慢移動的指針來模擬表示時間。 這兩種模式都是可以的,所以讓我們通過添加一個開關(guān)來讓兩種模式都可以設(shè)置。
添加另外一個public 字段到clock,命名為continuous。他可以開關(guān),因此我們可以使用布爾(boolean)類型,用bool來聲明它。
public Transform hoursTransform, minutesTransform, secondsTransform; </span><span style="background-color: #ffff00"><span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span> continuous;</span></pre>?
布爾類型的值要么是true(真),要么是false(假),也就對應(yīng)我們所說的了開和關(guān)。默認(rèn)情況下它是false的,所以一旦它出現(xiàn)在inspector里,讓我們打開它。
使用Continuous選項
?
現(xiàn)在我們有兩個模式了。 下一步,復(fù)制我們Update方法,將他們重命名為”UpdateContinuous”和”UpdateDiscrete”。
void UpdateContinuous () {}DateTime time = DateTime.Now;hoursTransform.localRotation =Quaternion.Euler(0f, time.Hour * degreesPerHour, 0f);minutesTransform.localRotation =Quaternion.Euler(0f, time.Minute * degreesPerMinute, 0f);secondsTransform.localRotation =Quaternion.Euler(0f, time.Second * degreesPerSecond, 0f);} </span><span style="background-color: #ffff00"><span style="color: #0000ff">void</span><span style="color: #000000"> UpdateDiscrete () {DateTime time </span>=<span style="color: #000000"> DateTime.Now;hoursTransform.localRotation </span>=<span style="color: #000000">Quaternion.Euler(0f, time.Hour </span>*<span style="color: #000000"> degreesPerHour, 0f);minutesTransform.localRotation </span>=<span style="color: #000000">Quaternion.Euler(0f, time.Minute </span>*<span style="color: #000000"> degreesPerMinute, 0f);secondsTransform.localRotation </span>=<span style="color: #000000">Quaternion.Euler(0f, time.Second </span>*<span style="color: #000000"> degreesPerSecond, 0f); }</span></span></pre>創(chuàng)建一個新的Update方法。 如果Continuous是true,就應(yīng)該調(diào)用UpdateContinuous方法。你可以用if語句來實現(xiàn)。If關(guān)鍵字后面是一個圓括號內(nèi)的表達(dá)式。如果那個表達(dá)式為真(true),那么內(nèi)部代碼段就會被執(zhí)行。否則,代碼段就會被跳過。
void Update () {if (continuous) {UpdateContinuous();}} Update方法應(yīng)該在什么地方被定義呢?要在Clock類里面。 相對于其他方法的位置無所謂。既可以在其他方法上面也可以在下面。
也可以添加一個替代的代碼段,當(dāng)表達(dá)式為假(false)的時候它會被執(zhí)行。 這是通過else關(guān)鍵字來實現(xiàn)的。 我們也能用這個來調(diào)用我們的UpdateDiscrete方法。
void Update () {if (continuous) {UpdateContinuous();}else {UpdateDiscrete();}}現(xiàn)在我們可以在兩種模式中切換了,但是他們做的事情都是一樣的。 我們需要調(diào)整UpdateContinuous,這樣他就顯示細(xì)微的小時,分鐘,秒的變化。 不幸的是,DateTime不包含這種方便的細(xì)微的數(shù)據(jù)。幸運(yùn)的是,它確實有個TimeOfDay屬性。它給我們了一個TimeSpan值,這個值包含我們需要格式的數(shù)據(jù),也就是TotalHours, TotalMinutes和TotalSeconds。
void UpdateContinuous () {TimeSpan time = DateTime.Now.TimeOfDay;hoursTransform.localRotation =Quaternion.Euler(0f, time.TotalHours * degreesPerHour, 0f);minutesTransform.localRotation =Quaternion.Euler(0f, time.TotalMinutes * degreesPerMinute, 0f);secondsTransform.localRotation =Quaternion.Euler(0f, time.TotalSeconds * degreesPerSecond, 0f);}但是這樣會導(dǎo)致編譯錯誤,因為新的數(shù)值被定義錯了類型。 他們被定義為雙精度浮點(diǎn)數(shù)值,也就是double類型。 這些數(shù)值提供了比float更高的精度。但是Unity的代碼只能支持單精度浮點(diǎn)類型。
?
單精度足夠準(zhǔn)確么? 對于大多數(shù)游戲來說,足夠了。 但是如果是非常遠(yuǎn)的距離或者大小區(qū)別的話,就會碰到問題。 這是你就要用一些小技巧,比如遠(yuǎn)距傳物來保持本地的游玩地區(qū)接近世界中間。當(dāng)用雙精度來解決這個問題的時候,會導(dǎo)致數(shù)值的大小也會改變,這樣就會導(dǎo)致性能問題。所以,大多數(shù)游戲引擎都用單精度。通過轉(zhuǎn)換double到float,可以解決這個問題。 這回拋棄我們不需要的那部分?jǐn)?shù)值精度。 這個過程被稱作數(shù)值轉(zhuǎn)換。將新類型用圓括號括起來放在數(shù)值前面,它就會被轉(zhuǎn)換了。
hoursTransform.localRotation =Quaternion.Euler(0f, (float)time.TotalHours * degreesPerHour, 0f);minutesTransform.localRotation =Quaternion.Euler(0f, (float)time.TotalMinutes * degreesPerMinute, 0f);secondsTransform.localRotation =Quaternion.Euler(0f, (float)time.TotalSeconds * degreesPerSecond, 0f);現(xiàn)在你應(yīng)該大致了解了Unity創(chuàng)建對象和腳本的基礎(chǔ)知識。
?
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的[Unity C#教程] 游戏对象和脚本的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux怎么查看sklearn版本,S
- 下一篇: 口袋之旅html5超强账号,口袋之旅特攻