// Unity學習筆記(零基礎入坑),自用的同時也許也能幫助別人
// 初學者,沒太多編程基礎,所以難免可能會有寫錯的地方,請見諒
//詳細教程視頻都有,筆記只記重點
傳送門: ?Unity學習筆記 - Roguelike Tutorial(上)
五、讓游戲元素動起來
1. 創(chuàng)建了一個基類
為玩家和敵人提供運動功能,玩家和敵人的腳本會繼承這個基類。
public abstract class MovingObject : MonoBehaviour {}
關于abstract:
The Abstract keyword enables us?to create classes and class members?that are incomplete and must be implemented?in the derived class.
https://msdn.microsoft.com/en-gb/library/sf985hc5.aspx
2.變量
public float moveTime = 0.1f;? ? //移動 1 point 的時間
public LayerMask blockingLayer;? //檢測碰撞的層
private BoxCollider2D boxCollider;
private Rigidbody2D rb2D;
private float inverseMoveTime;
3. 函數1 - Start函數
獲取到會用到的變量

4. 函數2 - 平滑運動函數 SmoothMovement
使玩家和敵人呈現(xiàn)連續(xù)、平滑的運動。
輸入目的地的Vector3即可實現(xiàn)當前物體向目的地的連續(xù)、平滑的直線運動
邏輯:和終點有距離——>向終點方向移動一“步”(inverseMoveTime * Time.deltaTime)并刷新距離——>停一幀——> 和終點有距離 ……

Magnitude是向量的長度,sqrMagnitude是長度的平方:
一個向量V的長度計算方法是Mathf.Sqrt(Vector3.Dot(v, v))。然而,sqrt計算是相當復雜的,需要更長的時間比正常執(zhí)行算術操作。計算長度平方來代替直接計算長度速度更快,除掉開根號計算是基本相同的。如果你只是使用大小比較的距離,那么你同樣可以比較平方長度大小來比較距離,得到相同的結果。
http://wiki.ceeger.com/script:unityengine:classes:vector3:vector3.sqrmagnitude
public static?Vector3?MoveTowards(Vector3?current,?Vector3?target, ?float maxDistanceDelta);
MoveToward的作用是將當前值current移向目標target。(對Vector3是沿兩點間直線)
maxDistanceDelta就是每次移動的最大長度。
返回值是當current值加上maxDistanceDelta的值,如果這個值超過了target,返回的就是target的值。
文/fan2b(簡書作者)
5. 函數3 - Move
輸入移動方向,返回是否有障礙及障礙物體
沒有障礙則觸發(fā)“平滑運動函數” SmoothMovement (有障礙的問題會在后兩個函數中處理)

“out”在這里被用來讓函數返回多個值,即不止返回一個bool值,還會返回一個raycastHit2D類型的值(hit)。
The out keyword causes arguments to be parsed by reference. In this case we're using it to return more than one value from our Move function.
boxCollider.enabled = false;
We're going to disable the attached box collider2D to make sure that when we are casting our ray that we're not going to hit our own collider.
hit = Physics2D.Linecast(start, end, blockingLayer);
Physics2D.Linecast會返回一個RaycastHit2D,即射線檢測到的第一個物體。
6. 函數4 - AttemptMove 獲取障礙物

接收Move函數返回的障礙物,
獲取到障礙物的component并作為參數傳入函數5 OnCantMove,并運行OnCantMove。
泛類型 <T>
public T GenericMethod <T>?(T param)
在函數被實際調用時,T會被具體的類型替代。
http://unity3d.com/cn/learn/tutorials/topics/scripting/generics
*之所以使用泛類型是因為,在繼承類player、和繼承類enemy中障礙的類型是不同的(墻、player)。
泛型類型約束——關鍵字 where
protected virtual void AttemptMove(intxDir,intyDir) ? ?where T:Component
{ ... }
限定泛類型的范圍
四種限制類型:
Class, Struct, new (), interfaces
https://msdn.microsoft.com/zh-cn/library/bb384067.aspx
7. 函數5 - OnCantMove
abstract函數,在基類中未定義具體功能,在繼承類中會被重寫,以實現(xiàn)在不同繼承類中的不同功能。

2016.08.10 這篇腳本?對一個零編程基礎的“玩家”真心夠復雜,而我的技巧就是適度地?不 求 甚 解!
六、墻的邏輯
C# Wall,撞一下會碎,撞多了會消失。

墻被撞的時候,調用函數DamageWall就可以了。
返回unity,要把這個腳本掛在8塊墻上,每塊墻的dmgSprite變量處要放入對應的被撞碎的墻的圖片。
七、Player動畫控制器設置
打開Player的Animator Controller。
1. 參數列表里,新建兩個trigger,一個用來觸發(fā)攻擊,一個用來觸發(fā)受傷。
2. 在PlayerIdle和PlayerAttack之間建立雙向Transitions
PlayerIdle --> PlayerAttack: Has Exit Time不勾選,即觸發(fā)后立即切換動畫
PlayerIdle <-- PlayerAttack: Has Exit Time勾選,即當前動畫結束后切換動畫,Exit Time =1,即完全播完后切換
Transition Duration都改為0,因為2D的基于sprite的動畫切換無法實現(xiàn)過渡,3D才需要。
3. 觸發(fā)條件設定:PlayerIdle --> PlayerAttack的Condition選上建好的Trigger
4. PlayerIdle和PlayerDamage同理
動畫轉換測試:

點擊左下參數列表里的Trigger即可觸發(fā)相應動畫:

2016.08.11?
八、Player的腳本 —— 控制player的移動
先在GameManager中補充了一些要用到的東西:


新建腳本Player:

1. 函數 - Start

重寫基類函數:
protected override void Function ()
? ? { ? ?重寫內容……
? ? ?base.Function();
? ? 重寫內容……?}
2. 函數 - CheckIfGameOver

3. 函數 - 重寫 AttemptMove

4. 自啟動函數 - Update

##兩次栽在Input.GetAxis的這個“Horizontal”上了,是“Horizontal”不是“Horizonal”啊!
調用AttemptMove,同時設定泛類型T-->Wall,并傳入獲取到的輸入參數。
*泛類型在函數被執(zhí)行時,需將泛類型具體化。
AttemptMove運行時會調用基類中的Move函數和OnCantMove函數,而Move函數又會調用SmoothMovement函數。
Input.GetAxisRaw:
Since input is not smoothed, keyboard input will always be either -1, 0 or 1. This is useful if you want to do all smoothing of keyboard input processing yourself.
5. 函數 - 重寫OnCantMove

6. 函數 - Restart

7. 自啟動函數 - OnTriggerEnter2D

8. 自啟動函數 - OnDisable
當前物體disable的時候,將分數值傳給GameManager

9. 函數 - LoseFood
當前腳本中未被調用,留在enemy腳本中產生攻擊時調用。

最后,Player腳本掛到player上,Blocking Layer選好。

2016.08.12 ?Fighting
九、Enemy的腳本 —— enemy的移動和攻擊
1. 繼承MovingObject,重寫Start函數

獲取自身動畫和player的transform。
2. 重寫AttemptMove函數

增加是否移動的判斷,enemy動一幀停一幀,不會連續(xù)動。
3. 新增函數 MoveEnemy —— 智能判斷趨向player的移動方向
這個函數的作用是判斷往哪個方向移動,先選X or Y,再選 正 or 負。

MoveEnemy是這個腳本的核心,它調用AttemptMove,而AttemptMove則調用其他基類中的函數。但enemy腳本中并不包含Update,MoveEnemy會被GameManager調用。這大概是因為當前是否讓enemy移動的判斷要寫在GameManager里,并且場景中會有多個enemy。
public void MoveEnemy()
This is going to be called by the GameManager when it issues the order to move to each of our enemies in our Enemies list.
Mathf.Abs()是取絕對值。
這里也凸顯了float.Epsilon作為一個無限接近于0的數值的作用。
條件表達式:
基本格式 ? ? ? ?表達式1? 表達式2:表達式3
1為真則返回2的值,1為假則返回3的值。
http://blog.csdn.net/liyzh_inspur/article/details/3080769
4. 重寫OnCantMove

最后要返回編輯器把腳本掛在兩個enemy上,把playerDamage參數設定上,還有blockingLayer。
2016.08.13?
十、Enemy動畫控制和行動觸發(fā)
1. enemy動畫控制器
跟player動畫控制器的操作基本相同
Enemy1Idle ---> Enemy1Attack :No Has Exit Time,Condition +Trigger "enemyAttack"
Enemy1Idle <--- Enemy1Attack :Has Exit Time, Exit Time = 1
Transition Duration都改為0
2. GameManager中調度enemy的移動
變量:

Awake新加List

每次開始新level,GameManager重新加載,Awake運行,清空上一關的敵人:

加敵人到列表的函數,在enemy腳本中調用:

協(xié)程,調用enemy腳本中的移動函數:

*為什么如果沒有敵人,要多停一下,player再動?不知道。
Update 調用協(xié)程MoveEnemies,MoveEnemies調用enemy上的MoveEnemy完成移動:

2016.08.14
PS: 今天測試的時候發(fā)現(xiàn),動畫Trigger名字的大小寫又搞錯了,然后OutWall沒有加Collider
2016.08.15