??在RPG類游戲中东且,除了有攻擊,也有和攻擊同等重要的動(dòng)作漠酿,那就是防御分冈。本節(jié)將討論如何實(shí)現(xiàn)防御基显。不過(guò)在有了實(shí)現(xiàn)攻擊的先例經(jīng)驗(yàn)椭员,我想實(shí)現(xiàn)防御的難度已經(jīng)降低了雨涛。
??先著手把動(dòng)畫機(jī)(Animator)里的動(dòng)畫準(zhǔn)備好:添加防御動(dòng)畫衍菱,采取與攻擊動(dòng)畫同樣的命名方式赶么,命名為defense1h;添加閑置(idle)動(dòng)畫鏈接Entry脊串;新增動(dòng)畫機(jī)bool型參數(shù)(Prameters)辫呻,命名為defense清钥;兩個(gè)動(dòng)畫間互拉Transition,都消勾Has Exit Time 放闺,添加觸發(fā)條件(Conditions)為defense參數(shù)祟昭,其中idle→defense1h的defense為true,defense1h→idle的defense為false雄人。
??現(xiàn)在我打算為這個(gè)模型添加一塊盾牌和木棍从橘,說(shuō)是這么說(shuō),其實(shí)添加Cube做盾牌础钠。Cylinder做木棍恰力。為此我們需要找到這個(gè)模型的右手手掌,和左手肘關(guān)節(jié)處旗吁。在模型的右手底下新增empty改名為WeaponHandle踩萎,再在WeaponHandle底下新增一3D Oeject Cylinder命名為sword:
??然后調(diào)整Cylinder的大小和旋轉(zhuǎn)角和WeaponHandle的位置,使其剛好在模型右手手掌的虎口處很钓,這樣角色在握拳時(shí)能剛好握住Cylinder而不穿模:
??現(xiàn)在來(lái)看看揮起來(lái)順不順手:
??看起來(lái)Ok∠愀現(xiàn)在來(lái)添加一塊盾牌,同樣找到左手肘關(guān)節(jié)處码倦,在其底下創(chuàng)建empty企孩,命名為WeaponHandle,再在WeaponHandle底下新增一3D Oeject Cube袁稽,命名為Shield:
??為什么不找左手(LeftHand)勿璃,手背放盾牌不是更好嗎?因?yàn)槲覀兊亩喂魟?dòng)畫有一個(gè)左手往外拐的動(dòng)作:
??如果戴上盾牌后推汽,有可能會(huì)造成穿模(手掌穿出盾牌)补疑,所以打算把盾牌放在肘關(guān)節(jié)以下的位置,通過(guò)偏斜一點(diǎn)盾牌的角度來(lái)避免這個(gè)問(wèn)題歹撒。
??通過(guò)調(diào)整WeaHandle的位置和shield的大小莲组,使其掌心不穿出Cube:
??現(xiàn)在來(lái)看看效果如何:
??因?yàn)殚e置動(dòng)畫的左手就是擺在胸前,導(dǎo)致其放上盾牌后有點(diǎn)變成像防御狀態(tài)的樣子暖夭。但是這并不是我想要的锹杈,不能讓玩家產(chǎn)生閑置狀態(tài)能擋住敵人攻擊的誤解。所以我們要改變?cè)陂e置動(dòng)畫下左手手臂的位置鳞尔。但是這個(gè)動(dòng)畫是是別人做好的嬉橙,我不會(huì)改啊,怎么辦寥假?不怕市框,Unity提供能用代碼改變動(dòng)畫中人物關(guān)節(jié)位置的方法,那就是
OnAnimatorIK()
糕韧,這個(gè)函數(shù)有點(diǎn)像我們之前用過(guò)的OnAnimatorMove()
枫振,前者是改變動(dòng)畫關(guān)節(jié)位置喻圃,后者處理動(dòng)畫的位移量。我們可以看看它的Signature和官方描述MonoBehaviour.OnAnimatorIK(int)
:
Parameters | Means |
---|---|
layerIndex | The index of the layer on which the IK solver is called. |
Description
Callback for setting up animation IK (inverse kinematics).OnAnimatorIK() is called by the Animator Component immediately before it updates its internal IK system. This callback can be used to set the positions of the IK goals and their respective weights.
??在解釋這個(gè)函數(shù)之前粪滤,我想先簡(jiǎn)單討論一下何為IK斧拍。IK,即Inverse kinematics杖小,反向動(dòng)力學(xué)肆汹。引用一處IK學(xué)習(xí)筆記中關(guān)于反向動(dòng)力學(xué)的解釋:
??反向動(dòng)力學(xué)與前向動(dòng)力學(xué)相反,先給出骨骼點(diǎn)的位置予权,然后要求出運(yùn)動(dòng)骨骼以什么樣的位移昂勉、旋轉(zhuǎn)、縮放可以使得骨骼點(diǎn)到達(dá)指定的位置扫腺。
??用加法來(lái)做比喻的話岗照,前向動(dòng)力學(xué)是已知算式1+1+3,求結(jié)果;反向動(dòng)力學(xué)是已知結(jié)果5笆环,求算式攒至。可以看出躁劣,反向動(dòng)力學(xué)計(jì)算得到的結(jié)果往往不是的迫吐,因此比前向動(dòng)力學(xué)要復(fù)雜許多。
??能看出其實(shí)IK是一門非常復(fù)雜的學(xué)科账忘,如果日后有機(jī)會(huì)必定深入學(xué)習(xí)渠抹。但今天我們僅僅是簡(jiǎn)單地使用一下OnAnimatorIK()
,不做深入討論闪萄。
??回到OnAnimatorIK()
上,這個(gè)函數(shù)會(huì)在動(dòng)畫組件更新它內(nèi)部的IK系統(tǒng)前被調(diào)用奇颠。用途是能夠用來(lái)設(shè)置IK目標(biāo)的位置和它們各自的權(quán)重败去。說(shuō)白了就是這個(gè)函數(shù)能在IK更新前,對(duì)動(dòng)畫的IK做最后的調(diào)整烈拒,這個(gè)調(diào)整就是對(duì)骨骼點(diǎn)的運(yùn)動(dòng)做位移圆裕、旋轉(zhuǎn)和縮放。那么究竟怎么做調(diào)整荆几,那就要用到另外的API了吓妆。
??我們想要的是把閑置動(dòng)畫里抬起來(lái)的左手給放到左大腿邊上,所以要對(duì)骨骼點(diǎn)進(jìn)行旋轉(zhuǎn)吨铸,這要用到兩個(gè)函數(shù):Animator.GetBoneTransform()
和Animator.SetBoneLocalRotation
行拢,接下來(lái)對(duì)這兩個(gè)函數(shù)分別做出解釋:
public Transform GetBoneTransform(HumanBodyBones humanBoneId)
:
Parameters | Means |
---|---|
humanBoneId | The human bone that is queried, see enum HumanBodyBones for a list of possible values. |
Description
Returns Transform mapped to this human bone id.
??如果想對(duì)某個(gè)骨骼點(diǎn)進(jìn)行一些操作,那就先獲得這個(gè)骨骼點(diǎn)的位置诞吱;要想獲得這個(gè)骨骼點(diǎn)的位置舟奠,就要用這個(gè)骨骼點(diǎn)對(duì)應(yīng)的枚舉值(enum)去查詢(queried)竭缝。等會(huì)用到這個(gè)函數(shù)時(shí)我們就知道怎么查詢左下臂。
public void SetBoneLocalRotation(HumanBodyBones humanBoneId, Quaternion rotation)
:
Parameters | Means |
---|---|
humanBoneId | The human bone Id. |
rotation | The local rotation. |
Description
Sets local rotation of a human bone during a IK pass.Can be used to create rotation IK goals for any human bone. Ex: Control lower and upper body independantly by setting Hips and Spine local rotation during an IK pass.
??這個(gè)函數(shù)就是對(duì)某指定的骨骼點(diǎn)進(jìn)行旋轉(zhuǎn)沼瘫。不難理解抬纸。描述給的例子是通過(guò)髖部和脊柱(Hips and Spine)的局部旋轉(zhuǎn)來(lái)獨(dú)立控制上下身體。
??我再重申一下我的需求:把閑置動(dòng)畫里抬起來(lái)的左手給放到左大腿邊上耿戚,但是在舉盾(進(jìn)入defense1h動(dòng)畫)時(shí)湿故,要取消這個(gè)修改不然這個(gè)舉盾動(dòng)畫也會(huì)被一并改掉。
??在準(zhǔn)備Add Component前膜蛔,需注意的是要想這個(gè)OnAnimatorIK()
生效坛猪,第一個(gè)要做的就是在defense Layer層級(jí)上把IK Pass上勾上,宣告我這個(gè)OnAnimatorIK()
是作用于此Layer的飞几,在之前實(shí)現(xiàn)一段攻擊時(shí)也有提到過(guò)這個(gè)砚哆。
??第二個(gè)就是把你寫的這個(gè)函數(shù)放在有Animator組件的對(duì)象的兄弟層級(jí)上,這里我就在Ybot上直接Add Component屑墨,命名為L(zhǎng)eftArmAnimFix:
??把其中的
Start()
和Update()
函數(shù)給砍了躁锁,這里用不上。然后宣告一個(gè)Animator對(duì)象卵史,在Awake()
獲得Ybot上的Animator組件:
private Animator anim;
void Awake(){
anim = GetComponent<Animator> ();
}
??然后實(shí)現(xiàn)OnAnimatorIK()
函數(shù)战转,思路是先在里面獲得左前臂的位置,然后宣告一個(gè)Vector3
變量以躯,把它曝露出去槐秧,方便我們對(duì)其作出修改(在改到合適位置后在把它寫死),然后再把這個(gè)Vector3
變量轉(zhuǎn)換為四元數(shù)喂給Animator.SetBoneLocalRotation()
忧设。
public class LeftArmAnimFix : MonoBehaviour {
private Animator anim;
public Vector3 tempLocalRotation;
void Awake(){
anim = GetComponent<Animator> ();
}
void OnAnimatorIK()
{
if (anim.GetBool("defense") == false) {
Transform leftArm = anim.GetBoneTransform (HumanBodyBones.LeftLowerArm);
leftArm.localEulerAngles = tempLocalRotation;
anim.SetBoneLocalRotation (HumanBodyBones.LeftLowerArm, Quaternion.Euler (leftArm.localEulerAngles));
}
}
}
??這個(gè)HumanBodyBones
的枚舉值有很多刁标,我們要的是左前臂,所以是LeftLowerArm:
??由于我們?cè)O(shè)置的歐拉角址晕,所以要把它轉(zhuǎn)換為四元數(shù)(函數(shù)要求)膀懈,轉(zhuǎn)換的方法就是
Quaternion.Euler()
。現(xiàn)在我們就可在外部調(diào)整我們的手臂了谨垃,記住先在Play mode下調(diào)整好启搂,然后Copy Component ,取消Play mode刘陶,再Paste Component Values胳赌。這是調(diào)好的角度,已經(jīng)基本實(shí)現(xiàn)了我的需求:??Last but not least, 實(shí)現(xiàn)舉盾動(dòng)作匙隔。這里我只給出手柄的實(shí)現(xiàn)疑苫,鍵盤就不再另說(shuō)了。首先在IUserInput.cs里宣告一bool變量命名為defense:
[Header("===== State =====")]
...
public bool defense;
??然后在JoystickInput.cs里用RB鍵舉盾:
//角色舉盾
defense = Input.GetButton(keyButRB);
??在ActorController.cs里,將輸入信號(hào)defense與動(dòng)畫機(jī)參數(shù)defense聯(lián)系起來(lái):
anim.SetBool ("defense",pi.defense);
??現(xiàn)在代碼方面完成了缀匕,但是我差點(diǎn)了忘了講兩件事情纳决,就是給defense Layer灌個(gè)Avatar Mask和它的權(quán)重。這里我把新建一個(gè)Avatar Mask乡小,只覆蓋左手阔加,命名為lefthand,然后灌給defense Layer:
??因?yàn)檫@層只覆蓋其他層動(dòng)畫的左手满钟,所以可以不考慮權(quán)重變化的問(wèn)題胜榔,直接拉滿就行:
??現(xiàn)在應(yīng)該就大功告成了,來(lái)看看效果: