1. 程式人生 > >Unity3D官方教程爬坑

Unity3D官方教程爬坑

全是在學官教時遇到的坑,然後數小時後爬出來.同時會新增到處學來的的Unity技巧

----------------------------------------------------------

程式碼:

1.使遊戲物件運動的N種方式

1、rigidbody.addforce(Vector3 * speed) (見roll-a-ball)

2、rigidbody.velocity(vector3 * speed);(見space shooter,改變位置向量,非常的生硬)


3、rigidbody.MovePositon(vector 3) & rigidbody.MoveRotation(quaternion)(包含移動和轉向,細膩,常用。見survival shooter player的移動)(轉向包括游標指向轉向,和鍵盤輸入轉向(見Tanks!))

4、transform.translate(方向 * 速度 * Time.deltatime)

見survival shooter內敵人被消滅後下沉並回收

2.鏡頭跟隨移動遊戲物件移動,指令碼內用LateUpdate()而非FixedUpdate()(見roll-a-balll)

3.Random.Range(min,max),如果兩個數都是float則前後都算,如果兩個數是int則包前不包後

4.FindObjectOfType<Inventory>()  vs  GetComponent<Inventory>()

FindObjectOfType①為外部呼叫,在所有場景和遊戲物件裡找,不是很快;通常放在Start()內。

GetComponent②為內部呼叫,只在掛著該指令碼的遊戲物件上的其他元件找,他們都在一個inspector裡,比如rigidbody,renderer,collider等。

②替代①的方式是給指令碼所在的物件選擇tag,再用【FindGameObjectWithTag("tag的名字“).GetComponent<指令碼>()】放入Awake()內。

也可以直接 在指令碼開頭public Inventory inventory; 然後在inspecotor介面把掛著Inventory指令碼的遊戲物件拖入框。[Adventure Game]

5.Debug

在space shooter中,行星在遊戲介面記憶體在,執行嘗試是否子彈能消除時發現行星不見了,這時候在行星的腳本里加了一段“Debug.Log(other.name)”把將行星摧毀的物體的名稱傳送到了unity的console內。

6.coroutine

在土豆視訊內spaceshoot官方教程中行星波生成裡解釋的很清楚(因為有中文字幕)。coroutine就是等某一段或幾段設定好的時間後迴圈方法內的程式碼。

呼叫這種非同步(即有時間間隔)的方法要用語句 startCoroutine (方法()); 。含非同步的方法要用IEnumerator來代替void,yield return new WaitForSecond(設定好的時間);是指等這段時間後繼續迴圈,寫在for語句內表示等N秒後i++,寫在迴圈前表示等N秒後開始while迴圈(把for迴圈放入while迴圈內,判斷條件可以按需求寫,在 space shooter裡想要的是行星不停出直到飛船被毀為止,所以判斷條件寫的是true(意思是true就是true,一直迴圈),再在while迴圈的末尾加上for迴圈的間隔)。

關於yield return,這篇文章就說的很清楚了:http://www.cnblogs.com/wangchengfeng/p/3724377.html

7.遊戲物件的回收(包括落下的行星,行星被射擊時的爆炸效果)

在space shooter內行星和飛行的子彈靠的是外接一個boundary(cube,box collider)並用指令碼(onTriggerExit)來回收遊戲物件。

但行星的爆炸特效並不與boundary碰撞,所以會冗餘。在所有爆炸特效下掛載指令碼在void start內用Destroy(gameObject, 設定的時間);語句使每幀檢測超過設定的時間後被掛載的遊戲物件銷燬,在survival shooter內對敵人的回收也是如此(先下沉,下沉N秒後摧毀)。

8.unity內指令碼的例項化關係

見space shooter內計分板的講解,即在指令碼內寫一個public的方法,要在其他指令碼內呼叫要先指定是要呼叫指令碼的哪個例項(即使在只有一個例項的情況下)。

9.Input.GetAxisRaw(); vs Input.GetAxis ();

Raw是指非平滑輸入,即輸入值只有-1,0,1(左,無輸入,右)。這種輸入的作用讓角色單位輸入變小,在操作感官上更有靈敏性,角色反應更快更流暢。(見survival shooter內Player的移動)


10.Time.time vs Time.deltaTime

Time.time是遊戲從開始到此刻的持續時間

Time.deltaTime是遊戲上一幀的持續時間(增量時間),每一幀完成的秒數都不同,所以在需要固定增量時(比如每秒移動10,每0.5秒攻擊一次),要在數值上乘以Time.deltaTime(即單位),否則將變成(每幀移動10,每幀攻擊2次)

11.在指令碼例項(A)內引用其他指令碼例項(B)內方法

【】如果A和B指令碼例項都掛在同一物件上(比如survival shooter的敵人上同時掛載Enemy Health和Enemy Attack),則在A內private EnemyAttack enemyAttack;
然後在Awake()內  enemyAttack = GetComponent<EnemyAttack> ();就可以直接呼叫了。

【】如果A和B在不同物件上(比如survival shooter的Enmey Attack和Player Health)則在A內private PlayerHealth playerHealth;

然後在Awake()內先要player = GameObject.FindGameObjectWithTag<"Player">();找到掛B指令碼的物件(該物件必須新增“Player”的Tag)。

然後再playerHealth = player.GetComponent<PlayerHealth>();,才可呼叫。

如果是在場景裡的GameObject上掛的指令碼則可直接Public GameObject xx; 然後在Inspector裡拖入。但如果是存成Prefab的的遊戲物件上掛的指令碼,引用方式必須是上面通過Tag尋找,而且要注意指令碼載入順序找不到引用報錯。

還有一種見Adventure Game(僅用於Editor繼承),下面的target是Editor特有的欄位,表示該Editor的物件,第二個紅框裡的句子只是把target轉換成了ConditionCollection型別,在這之前target僅僅是個Object型別不明

【】如果B在A的子物件上(比如survival shooter的PlayerShooting掛在遊戲物件Player的子物件GunBarrelEnd上),在A內引用B的則private PlayerShooting playerShooting;

然後在Awake內playerShooting = GetComponentInChildren<PlayerShooting>();

【】還有一種情況,玩家開槍射擊敵人,使敵人生命值降低。player下的遊戲物件槍上所掛的指令碼PlayerShooting要引用某個敵人上的EnemyHealth來呼叫生命值降低方法。

則以射線碰撞Raycast,撞到層為“shootable”後返回碰撞點資訊,在資訊點上再找其指令碼例項。(見survival shoot的playershooting指令碼)

【】如果一個變數宣告方式為public static xx(型別) xx(名稱),則在其他指令碼內呼叫不用先宣告上面的那一套,直接使用就是。(一般用於分數等不可改元素)

【】遊戲內的GameController,SoundController,DataBase等都可以直接在場景裡建同名空物件,掛上對應的指令碼,設定他們用單例在Awake里加載比較不容易出錯。特別是在不同物件在不同場景裡無法拖拽引用的情況(見Quiz Game)

【】上面說到trigger可以探測是否掛載特定指令碼(EnemyHealth)來判斷是否是指定物件(Enemy)。

比如在GameGrind裝備系統教程裡裝備疊加方法中,就用到指定格子下裝備上的指令碼

那如果要更新該裝備下的Text呢,則是該指令碼.transform就指代該指令碼所在的遊戲物件。


12.指令碼頭的“using ...”(見2D Roguelike 4內BoardManager蠻牛)

using System; 這樣就可以在腳本里使用Serializable屬性。Serializable可以讓變數在Inspector裡展開或收起。

using System.Collections.Generic;,這樣可以使用List

using Random = UnityEngine.Random; 因為系統和unity引擎都有 Random的名稱空間。

using UnityEngine.EventSystem; 為了使用Eventsystem的功能,比如在揹包拖拽裝備(見裝備系統)

using System.IO; 為了查詢載入檔案,比如LitJson(見裝備系統)

13.將腳本里的Public屬性隱藏,或顯示Private屬性

有時一些Public屬性我們需要在其他指令碼呼叫,但已經調整好了,不想再Inspector內顯示

可以在指令碼內該屬性的上一行加上【HideInInspector】該屬性將不會再Inspector面板內顯示

顯示private屬性則在屬性上方加上【SerializeField】。

如果不想加語句直接想看到一個指令碼內的所有變數並除錯,Unity有Debug模式,會顯示該遊戲物件的所有隱藏元件,包括其指令碼的所有屬性。Inspector面板右上角選擇Debug就好了。

14.指令碼內程式碼分塊

有時候程式碼很長,想分塊程式碼並收起以方便檢視,可在想收起的程式碼塊上一行寫  #region 程式碼塊名稱,結尾寫#endregion。

15.for,switch迴圈和while,if等自動補全程式碼格式

比如for迴圈 for(int i =0; i <`````),可以打下for後按兩下Tab鍵系統自動補齊格式

16.指令碼內引用工程專案內檔案的路徑變動問題

在裝備系統教程裡,我們引用了記錄了裝備資訊Json的檔案,如果打包遊戲的話會發現遊戲無法正常讀取該檔案,是因為隨著打包檔案,Json檔案的路徑也會變化,這時候把該檔案放在一個Unity規定的特定資料夾,並用其他語句引用。

具體參見Unity Manual:Streaming Assets頁面

17.指令碼內註釋每個欄位並在unity Inspector上懸停顯示欄位

引數欄位太多了不知道什麼意思,在宣告句上加上[Tooltip(“XX”)],在unity內滑鼠停在該屬性上出現註釋

18.讓Unity 內的Button變灰不能摁

用Button.Interactable = false;

19.程式碼內引用其他GameObject的方法(Find & FindWithTag)

Unity裡有3種引用方式

Find 和 FindWithTag都需要該GameObject必須時active的。

1.比如點選Button彈出一個介面,這個介面在平時是隱藏的。

操作方法是:在Unity裡該介面Active,然後在Start()或Awake()裡用Find或FindWithTag找到然後再SetActive(false),然後就可以使用了。

Find是在所有Active的遊戲物件裡找,FindWithTag是在所有標籤物件裡找,所以FindWithTag效率更高,官方不建議在Update()裡使用Find因為太耗效能。

2.再比如經常要檢測其他物件,比如trigger裡的是Enemy還是其他的東西。

這時候除了給Enemy標上Tag,檢測other.tag == "Enemy"之外,還有一種方法是if(!other.GetComponent<EnemyHealth>())通過檢測trigger物上有沒有指定指令碼來判斷是否是制定物。

20.指令碼內比較字串大小

可直接使用if(str1 == str2)或 if(str1.Equals(str2)) 

注意string是區分大小寫的,我在比較後List元素Add,不停跳出索引有問題的Bug。花了2個半小時才找到原來是str1的首字母時大寫的,str2的首字母小寫,結果全是false導致沒有任何元素新增進List。

21.List清除元素/查詢元素/檢視是否包含元素

List.Clear(); 清除List內的所有元素,保持List的基本屬性(比如List<Enemy> enemy清空後沒有元素,但屬性仍然是<Enemy>)。

List.Remove(xxx); 清除xxx表示式滿足的所有元素,比如刪除一個List<int>內為偶數的所有元素:

參考:http://blog.csdn.net/yl2isoft/article/details/17059093

參考:https://msdn.microsoft.com/en-us/library/wdka673a(v=vs.110).aspx

List.Find(xx)

如果找到該語句要求的元素則返回該元素,如果沒找到則返回的是該元素型別的預設值,而不是null。參考

List.Contains()

如果找到返回true,其他情況返回false。參考

List.FindIndex(xx)

如果找到該語句要求的元素則返回該元素的的索引,否則返回-1。參考

List的用法 參考

22. console提示:NullReferenceException: Object reference not set to an instance of an object

意思為沒有引用.

一種可能是該指令碼A引用條目本體所在的指令碼B,的執行速度要快,所以造成沒有引用。這時候調整指令碼執行順序就好。

另一種可能是該Object本體就在該指令碼,那麼在指令碼初始宣告該Object時沒有初始化。

(比如把PlayerData playerData = new PlayerData()裡後面的初始化語句忘了就會出現上述BUG)

還有一種可能是Prefab在面板上未Apply,比如Prefab GameManager要拖拽引用 DataManager,DataManager必須也存成Prefab,然後把作為Prefab的DataManager拖入才算是引用成功。如果在Hierarchy下的DataManager新增了指令碼卻忘記Apply,則Prefab DataManager沒有掛載新指令碼,GameManager在執行時就找不到該指令碼而報錯。

23.Destory DontDestoryOnLoad GameObject

GameManager通常是DontDestoryOnLoad的,比如Roguelike裡就一直存在。那如果想從某一場景切換到另一場景時會發現原先場景的GameManager還是存在,這時候要刪除該GameManager GameObject有兩種方式:

參考

1.

2.

不過圖中的處理不是太好,應該在GameManager 物件所掛的指令碼上,寫入上面的代理。而代理的啟用&取消通常在OnEnable() & OnDisable()裡,代理必須取消,否則將導致記憶體洩漏。

關於delegates和C#內Events的說明見Adventure Game教程。教程

24.Unity內的雙擊

using UnityEngine;  
using UnityEngine.UI;  
using UnityEngine.EventSystems;  
  
public class MJCard : MonoBehaviour,IPointerClickHandler {  
  
    float lastClickTime;  //不要申明在方法內
 
    public void OnPointerClick(PointerEventData eventData)  
    {    
        if (Time.time - lastClickTime < 0.2)  
        {  
            Debug.log("雙擊");  
        }  
       lastClickTime = Time.time;  
    }

25.點選框外區域關閉對話方塊(Popup)

要點選對話方塊Child外區域關閉Child的最簡單辦法:新建一個Panel命名為Parent,拉伸到全屏,設為透明,把child拉為子物件。然後在該Parent上掛指令碼

點選透明Panel就會關閉子物件。

26.物體拋物線移動

27.怎樣修改Image的alpha

不可以直接image.color.a進行修改,會提示無法轉換,必須中轉一下。

28.怎樣使物件在移動時轉向(比如在2D裡)

一般向左向右都是改scale,比如原本向右的改為向左就把scale.x改為-1,移動的時候改transform.localscale。

但這一項不可以直接修改,而是要像image.color.a一樣中轉一下。

參考:https://www.youtube.com/watch?v=e7I315b74HY

29.怎樣平滑的在N秒內填充slider

30.用MD5加密資料的方法

參考來源

31.Unity中漸隱和閃爍的實現

參考

32.Unity內載入(Load)資源

比如載入image的物件Sprite,一般我們的資源都放在Resourses資料夾(注意,一定是複數形式),用Resources.Load<你要載入的形式,比如Sprite>(“該檔案在Resources資料夾下的路徑”)就好了。

33.Unity修改transform.rotation的兩種方法

transform.localPosition和transform.localScale都是直接賦值三元數,給旋轉賦值需要用 
  方法一: 
  xxx.transform.localEulerAngles = new Vector3 (0.0f,0.0f,0.0f); 
  方法二: 
   xxx.transform.rotation=Quaternion.Euler(0.0f,0.0f,0.0f);
參考

34.Json內中文為解析後為亂碼的解決

json檔案一般都是用記事本開啟的,開啟後另存為選擇編碼方式為UTF-8儲存並覆蓋就可以顯示了。參考

Unity:

1.無法建立新專案,重啟Unity,或者重新登入Unity賬號。

2.打包釋出時,Build Settings 提示”Because you are not a member of this project this build will not access Unity services.”,重啟Unity,或者重新登入Unity賬號。

3.2D遊戲更改整體背景大小,不要設定背景的Scale,設定攝像機的Size。

4.Console提示

儲存了腳本回到unity發現提示“There are inconsistent line endings in the 'Assets/Scripts/CameraController.cs' script. Some are Mac OS X (UNIX) and some are Windows.”?,點選VS下File→Advanced Save Options→Line Endings選擇Windows(CR LF),確定後再儲存一遍OK了。

或者在VS內先關閉這個script,再在File下“最近使用過的檔案”開啟該script,VS會跳出對話方塊,YES後再儲存一遍就好。

5.碰撞器(Collider)判斷

是否有接觸的條件是兩方都有碰撞器,且至少一方有rigidbody元件。如果任何一方勾選了Is Trigger(勾選這個的是被碰撞物件,比如小方塊被碰撞後消失,則勾選小方塊,而不是小球),可觸發檢測是否進入碰撞範圍,並按指令碼對應表現。所以雙方都沒勾選觸發,可碰撞,不可有其他互動。

6.盒子內的碰撞

要一個平面上的球不滾出平面,直接為平面加Box Collider是不可行的,球滾到邊緣會直接粘到平面外緣不動了。自身多個碰撞器重疊如果有一個以上未勾選Istrigger會把重疊部分處理成一體。所以要在平面四條邊放四個條狀碰撞器粘成一個框狀的。[2D  UFO]

7.2d模式下所有動的遊戲物件(即使只是旋轉,不移動)也要新增rigidbody2D

不然儘管掛上指令碼不新增rigidbody也可以移動,但是unity每一幀都會重新定位載入計算該物件,會對效能產生極大損耗。[2D UFO]

8.Rigidbody.isKinematic

在指令碼和rigidbody元件內選項使用。物件選擇Kinematic後,碰撞、力、關節不再對物件產生影響。該物件的移動或運動將完全由Animation(動畫)控制或者指令碼編輯物件的位置改變來控制。(2D Roguelike,不想player滿場亂飛,就是一步一移動,所以用這個。之前的SpaceShooter,RollABall等沒有勾選此選項,即預設的Dynamic。Static是將物件設為像背景一樣的物件,不可移動不可受力,但是可以和其他dynamic物體碰撞)

9.unity中的sprite(精靈)

unity2D遊戲中的所有物件都叫精靈(比如2D Roguelike中的Player,Enemy,以及Floor這些不動的元素),精靈每個都用Sprite Renderer而不是Mesh Renderer。

10.Layer vs Tag

tag是為了對object分類,通常用於指令碼(GameObject.FindWithTag())來快速搜尋物件。Layer用於區分攝像機的渲染&射線投射物件,燈光的特定照射和物體的碰撞等。Layer雖然可以自己新增並命名,但layer在系統內是以編號區分的,所以layer的屬性是int。(比如 int blockingLayer)。sorting layer在Sprite Renderer選項內,因為2D物體是可以覆蓋的,所以Sorting Layer最底層的最先渲染,處於場景的最上方。

11.UNITY裡的文字

unity的文字層和遊戲層不同,遊戲層以畫素計數,文字層以比例計數,左下角為(0,0),右上角為(1,1),所以要把文字放在左上角只要更改position為(0,1,0)。

要是想給文字留些空隙而不是死貼著邊框,在pixel offset裡調整(x,y)的畫素相對移動(見space shooter內文字的設定)

12.Animation元件內Parameters trigger vs bool

動畫狀態轉換的引數有四種類型:int,float,bool,trigger。前三種都很好理解,第四種是一次性的bool,比如人物死亡觸發trigger,發呆和行走轉換用bool。

比如SurvivalShooter里人物行走用bool來轉換

像攻擊比如Roguelike裡的Chop和Hit就用trigger來轉換。NPC釋放一次性動畫用Trigger。

13.Unity裡快速查詢Component講解

   不用在瀏覽器內開啟官網再輸入查詢,直接點元件右上角的小書圖示。

14.場景搭建時用Snap Setting

比如一排圍牆,普通用Duplicate,然後手動一點一點挪(比如roll-a-ball),要對齊及其痛苦。我們可以在上方工具欄Edit→Snap Setting裡設定好移動單位,移動下一個Duplicate Object時按住Ctrl鍵同時移動,就可以設定好的單位輕鬆移好。選定一堆小方塊,然後摁Snap All Axes就會自動幫排列整齊(像Word裡的對齊)。

15.鎖定Inspector面板方便拖拽物件或屬性

 Unity經常需要確定元件內引用的是哪個物件,我們常在Hierarchy或Project下找到物件然後拖進欄內,但有時檔案太多,點選到物件Inspector直接顯示該物件的元件群,而不是我們需要拖拽的元件群。這時先把Inspector鎖住,再開始查詢,拖拽就很方便了。

16.Unity Color取色

在選擇如背景Color時,點選小滴管可以取色,不僅可以取Unity Scene窗口裡的顏色,也可以取其他顏色,比如一幅網上圖取色,我們把瀏覽器和Unity並行放置,點選取色然後點選圖片上需要的顏色就可以了。

17.Unity工程2D和3D模式的區別

unity其實不存在2D或3D的區別,只是攝像機是Orthographic或Perspective的區別。正交模式下鏡頭沒有Z軸就成了2D模式。

具體區別:

2D:

鏡頭為Orthographic;

Scene面板為2Dmode;

Windows→Lighting→Setting裡Skybox為None,AmbientSource為color,Realtime Lighting和Mixed Lighting都取消GI勾選。

3D:

鏡頭為Perspective;

Scene面板為3Dmode;

Skybox為Default,AmibientSource為Skybox,兩個選項都勾選。

18.Unity內指令碼載入順序

Edit→Project Settings→Script execution Order裡可以設定各指令碼的執行順序和指定多少時間後該指令碼開始載入。

為了防止這種BUG:指令碼引用json資料,新增json資料List指令碼還沒載入完,搜尋資料並執行其他方法的指令碼已經開始運行了。

包括GameController,DataBase等需要提前載入的指令碼可放到前面。

19.Unity內image/button等上面疊加序列幀動畫

比如在場景切換時一般就顯示一個黑圖做幕布的效果(比如2DRougelike裡的level升級時的Day N),如果想要在這塊黑布上再播放動畫,把動畫拖入Canvas或給黑布Image下加Animator都是沒有用的。

比如UICanvas下有一個UIImage為黑色,UIImage下有一個UIText用來顯示Day 1.現在要再UIImage上顯示幀動畫。則再UIImage下再新建一個子Image,比如命名為AnimationImage,然後再選中AnimationImage的情況下,開啟Animation視窗,點選creat,選好名字和資料夾,新建好動畫後,把序列幀動畫包含的sprite全部拖入Animation視窗,調整下播放速度就可以了。

參考:http://blog.csdn.net/mynameiszhuli/article/details/77951668

20.在NPC頭上顯示對話方塊,或地圖上顯示指引箭頭

這時候UI作為整個地圖的一部分,應該新建一個Canvas設定為WorldSpace。把Canvas拉到所需大小和位置。

比如對話方塊,有Panel有Text,單純設定WorldSpace是沒法正確顯示的。

應該將Dynamic Pixels Per Unit設定的儘量大一些,這個選項表示如Text等的每一單位渲染的畫素,值越大Text可以顯示的越小。

然後Reference Pixels Per Unit設定為1,表示圖片等資源等比畫素畫素,這樣就不會造成如Panel,Image等的虛化。

比如NPC頭頂的DialogueBox,NameText字號選擇為1,調整canvas畫素大小,然後把DialogueText設定為Best Fit,就會自適應Text大小。

21.血條跟隨

血條小小的跟在玩家頭上,需要Slider所在的Canvas跟隨當前主相機。參考