// Unity學(xué)習(xí)筆記(零基礎(chǔ)入坑),自用的同時(shí)也許也能幫助別人
// 初學(xué)者,沒太多編程基礎(chǔ),所以難免可能會有寫錯(cuò)的地方,請見諒
//詳細(xì)教程視頻都有,筆記只記重點(diǎn)
傳送門: ?Unity學(xué)習(xí)筆記 - Roguelike Tutorial(上)
五、讓游戲元素動(dòng)起來
1. 創(chuàng)建了一個(gè)基類
為玩家和敵人提供運(yùn)動(dòng)功能,玩家和敵人的腳本會繼承這個(gè)基類。
public abstract class MovingObject : MonoBehaviour {}
關(guān)于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;? ? //移動(dòng) 1 point 的時(shí)間
public LayerMask blockingLayer;? //檢測碰撞的層
private BoxCollider2D boxCollider;
private Rigidbody2D rb2D;
private float inverseMoveTime;
3. 函數(shù)1 - Start函數(shù)
獲取到會用到的變量
4. 函數(shù)2 - 平滑運(yùn)動(dòng)函數(shù) SmoothMovement
使玩家和敵人呈現(xiàn)連續(xù)、平滑的運(yùn)動(dòng)。
輸入目的地的Vector3即可實(shí)現(xiàn)當(dāng)前物體向目的地的連續(xù)、平滑的直線運(yùn)動(dòng)
邏輯:和終點(diǎn)有距離——>向終點(diǎn)方向移動(dòng)一“步”(inverseMoveTime * Time.deltaTime)并刷新距離——>停一幀——> 和終點(diǎn)有距離 ……
Magnitude是向量的長度,sqrMagnitude是長度的平方:
一個(gè)向量V的長度計(jì)算方法是Mathf.Sqrt(Vector3.Dot(v, v))。然而,sqrt計(jì)算是相當(dāng)復(fù)雜的,需要更長的時(shí)間比正常執(zhí)行算術(shù)操作。計(jì)算長度平方來代替直接計(jì)算長度速度更快,除掉開根號計(jì)算是基本相同的。如果你只是使用大小比較的距離,那么你同樣可以比較平方長度大小來比較距離,得到相同的結(jié)果。
http://wiki.ceeger.com/script:unityengine:classes:vector3:vector3.sqrmagnitude
public static?Vector3?MoveTowards(Vector3?current,?Vector3?target, ?float maxDistanceDelta);
MoveToward的作用是將當(dāng)前值current移向目標(biāo)target。(對Vector3是沿兩點(diǎn)間直線)
maxDistanceDelta就是每次移動(dòng)的最大長度。
返回值是當(dāng)current值加上maxDistanceDelta的值,如果這個(gè)值超過了target,返回的就是target的值。
文/fan2b(簡書作者)
5. 函數(shù)3 - Move
輸入移動(dòng)方向,返回是否有障礙及障礙物體
沒有障礙則觸發(fā)“平滑運(yùn)動(dòng)函數(shù)” SmoothMovement (有障礙的問題會在后兩個(gè)函數(shù)中處理)
“out”在這里被用來讓函數(shù)返回多個(gè)值,即不止返回一個(gè)bool值,還會返回一個(gè)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會返回一個(gè)RaycastHit2D,即射線檢測到的第一個(gè)物體。
6. 函數(shù)4 - AttemptMove 獲取障礙物
接收Move函數(shù)返回的障礙物,
獲取到障礙物的component并作為參數(shù)傳入函數(shù)5 OnCantMove,并運(yùn)行OnCantMove。
泛類型 <T>
public T GenericMethod <T>?(T param)
在函數(shù)被實(shí)際調(diào)用時(shí),T會被具體的類型替代。
http://unity3d.com/cn/learn/tutorials/topics/scripting/generics
*之所以使用泛類型是因?yàn)椋诶^承類player、和繼承類enemy中障礙的類型是不同的(墻、player)。
泛型類型約束——關(guān)鍵字 where
protected virtual void AttemptMove(intxDir,intyDir) ? ?where T:Component
{ ... }
限定泛類型的范圍
四種限制類型:
Class, Struct, new (), interfaces
https://msdn.microsoft.com/zh-cn/library/bb384067.aspx
7. 函數(shù)5 - OnCantMove
abstract函數(shù),在基類中未定義具體功能,在繼承類中會被重寫,以實(shí)現(xiàn)在不同繼承類中的不同功能。
2016.08.10 這篇腳本?對一個(gè)零編程基礎(chǔ)的“玩家”真心夠復(fù)雜,而我的技巧就是適度地?不 求 甚 解!
六、墻的邏輯
C# Wall,撞一下會碎,撞多了會消失。
墻被撞的時(shí)候,調(diào)用函數(shù)DamageWall就可以了。
返回unity,要把這個(gè)腳本掛在8塊墻上,每塊墻的dmgSprite變量處要放入對應(yīng)的被撞碎的墻的圖片。
七、Player動(dòng)畫控制器設(shè)置
打開Player的Animator Controller。
1. 參數(shù)列表里,新建兩個(gè)trigger,一個(gè)用來觸發(fā)攻擊,一個(gè)用來觸發(fā)受傷。
2. 在PlayerIdle和PlayerAttack之間建立雙向Transitions
PlayerIdle --> PlayerAttack: Has Exit Time不勾選,即觸發(fā)后立即切換動(dòng)畫
PlayerIdle <-- PlayerAttack: Has Exit Time勾選,即當(dāng)前動(dòng)畫結(jié)束后切換動(dòng)畫,Exit Time =1,即完全播完后切換
Transition Duration都改為0,因?yàn)?D的基于sprite的動(dòng)畫切換無法實(shí)現(xiàn)過渡,3D才需要。
3. 觸發(fā)條件設(shè)定:PlayerIdle --> PlayerAttack的Condition選上建好的Trigger
4. PlayerIdle和PlayerDamage同理
動(dòng)畫轉(zhuǎn)換測試:
點(diǎn)擊左下參數(shù)列表里的Trigger即可觸發(fā)相應(yīng)動(dòng)畫:
2016.08.11?
八、Player的腳本 —— 控制player的移動(dòng)
先在GameManager中補(bǔ)充了一些要用到的東西:
新建腳本Player:
1. 函數(shù) - Start
重寫基類函數(shù):
protected override void Function ()
? ? { ? ?重寫內(nèi)容……
? ? ?base.Function();
? ? 重寫內(nèi)容……?}
2. 函數(shù) - CheckIfGameOver
3. 函數(shù) - 重寫 AttemptMove
4. 自啟動(dòng)函數(shù) - Update
##兩次栽在Input.GetAxis的這個(gè)“Horizontal”上了,是“Horizontal”不是“Horizonal”啊!
調(diào)用AttemptMove,同時(shí)設(shè)定泛類型T-->Wall,并傳入獲取到的輸入?yún)?shù)。
*泛類型在函數(shù)被執(zhí)行時(shí),需將泛類型具體化。
AttemptMove運(yùn)行時(shí)會調(diào)用基類中的Move函數(shù)和OnCantMove函數(shù),而Move函數(shù)又會調(diào)用SmoothMovement函數(shù)。
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. 函數(shù) - 重寫OnCantMove
6. 函數(shù) - Restart
7. 自啟動(dòng)函數(shù) - OnTriggerEnter2D
8. 自啟動(dòng)函數(shù) - OnDisable
當(dāng)前物體disable的時(shí)候,將分?jǐn)?shù)值傳給GameManager
9. 函數(shù) - LoseFood
當(dāng)前腳本中未被調(diào)用,留在enemy腳本中產(chǎn)生攻擊時(shí)調(diào)用。
最后,Player腳本掛到player上,Blocking Layer選好。
2016.08.12 ?Fighting
九、Enemy的腳本 —— enemy的移動(dòng)和攻擊
1. 繼承MovingObject,重寫Start函數(shù)
獲取自身動(dòng)畫和player的transform。
2. 重寫AttemptMove函數(shù)
增加是否移動(dòng)的判斷,enemy動(dòng)一幀停一幀,不會連續(xù)動(dòng)。
3. 新增函數(shù) MoveEnemy —— 智能判斷趨向player的移動(dòng)方向
這個(gè)函數(shù)的作用是判斷往哪個(gè)方向移動(dòng),先選X or Y,再選 正 or 負(fù)。
MoveEnemy是這個(gè)腳本的核心,它調(diào)用AttemptMove,而AttemptMove則調(diào)用其他基類中的函數(shù)。但enemy腳本中并不包含Update,MoveEnemy會被GameManager調(diào)用。這大概是因?yàn)楫?dāng)前是否讓enemy移動(dòng)的判斷要寫在GameManager里,并且場景中會有多個(gè)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作為一個(gè)無限接近于0的數(shù)值的作用。
條件表達(dá)式:
基本格式 ? ? ? ?表達(dá)式1? 表達(dá)式2:表達(dá)式3
1為真則返回2的值,1為假則返回3的值。
http://blog.csdn.net/liyzh_inspur/article/details/3080769
4. 重寫OnCantMove
最后要返回編輯器把腳本掛在兩個(gè)enemy上,把playerDamage參數(shù)設(shè)定上,還有blockingLayer。
2016.08.13?
十、Enemy動(dòng)畫控制和行動(dòng)觸發(fā)
1. enemy動(dòng)畫控制器
跟player動(dòng)畫控制器的操作基本相同
Enemy1Idle ---> Enemy1Attack :No Has Exit Time,Condition +Trigger "enemyAttack"
Enemy1Idle <--- Enemy1Attack :Has Exit Time, Exit Time = 1
Transition Duration都改為0
2. GameManager中調(diào)度enemy的移動(dòng)
變量:
Awake新加List
每次開始新level,GameManager重新加載,Awake運(yùn)行,清空上一關(guān)的敵人:
加敵人到列表的函數(shù),在enemy腳本中調(diào)用:
協(xié)程,調(diào)用enemy腳本中的移動(dòng)函數(shù):
*為什么如果沒有敵人,要多停一下,player再動(dòng)?不知道。
Update 調(diào)用協(xié)程MoveEnemies,MoveEnemies調(diào)用enemy上的MoveEnemy完成移動(dòng):
2016.08.14
PS: 今天測試的時(shí)候發(fā)現(xiàn),動(dòng)畫Trigger名字的大小寫又搞錯(cuò)了,然后OutWall沒有加Collider
2016.08.15