unity3d學習筆記(七)--利用單例指令碼實現英雄與怪物的攻擊與受擊
本系列文章由Aimar_Johnny編寫,歡迎轉載,轉載請標明出處,謝謝。
http://blog.csdn.net/lzhq1982/article/details/12653945
我們的世界有了怪物,那麼你怎麼忍心不去虐他們一下,勇士,揮舞你的大刀,去砍他們吧。呃,有點血腥,少兒不宜。
如上一篇所說,我這裡的互動全是在單例指令碼中實現的。命名為BattleScene,單例指令碼負責事件的分發和傳遞,Hero和Monster指令碼負責觸發和接收處理訊息,設計理念是在Hero指令碼中你絕對看不到Monster,同樣,Monster指令碼中,你也看不到Hero,他們都在單例指令碼中,單例指令碼中有這麼一段核心的訊息傳遞程式碼:
public void SendGameMessage<T>(MESSAGE_TYPE type, T t) { switch (type) { case MESSAGE_TYPE.MESSAGE_HERO_RUN_AT_TARGET: _hero.SendMessage("RunAtTarget", t); break; case MESSAGE_TYPE.MESSAGE_HERO_BE_HURT: _hero.SendMessage("ReduceHp", t); break; case MESSAGE_TYPE.MESSAGE_ENEMY_BE_HURT: if (_enemy) _enemy.SendMessage("BeHurt"); break; case MESSAGE_TYPE.MESSAGE_ENEMY_REDUCE_HP: if (_enemy) _enemy.SendMessage("ReduceHp", t); break; } }
每一條訊息傳遞一個互動,現在看不懂沒關係,下面我再細細講解,只是這裡的<T>,如果有人不清楚,可以說一下,它是C#裡的泛型的概念,C++裡也有類似的模板,說白了,就是可以代替你想要的型別,用處很廣,這裡用來傳遞數值,因為不知道數值會是什麼型別,所以泛型就大顯神威了。
言歸正傳,我的英雄和怪物的互動流程是這樣的,點選一個怪->英雄跑過去->英雄發起攻擊->怪物受擊->怪物反擊->英雄受擊->英雄繼續攻擊->如此反覆,直到一方死去。下面就按照這個流程看看我是怎麼實現的。
1、點選一個怪
void OnMouseDown() { BattleScene.GetInstance().SendGameMessage<GameObject>(BattleScene.MESSAGE_TYPE.MESSAGE_HERO_RUN_AT_TARGET, gameObject); }
這是怪物指令碼上的響應滑鼠點選的函式,就一句,向單例指令碼發訊息,第一個引數是訊息型別,第二個引數是傳遞的數值,這裡傳的是怪物的gameobject。然後你可以對照上面的訊息傳遞程式碼,向hero指令碼的RunAtTarget()這個函式傳遞訊息,意思是英雄啊,你該向那個怪跑了。
2、英雄跑過去
void RunAtTarget(GameObject obj)
{
BattleScene.GetInstance().Enemy = obj;
curState = en_state.en_state_run;
isHunting = true;
}
case en_state.en_state_run:
curAnimClip = animation["Run"].clip;
gameObject.animation.CrossFade(curAnimClip.name);
Vector3 forward = transform.TransformDirection(Vector3.forward);
_controller.SimpleMove(forward * runSpeed);
if (BattleScene.GetInstance().Enemy) {
smoothRotate(BattleScene.GetInstance().Enemy.transform.position);
} else if (runTarget != Vector3.zero) {
。。。
}
break;
RunAtTarget的引數傳遞的是怪物的gameobject,但我不會將這個怪物存在hero指令碼中,我把它傳給了單例指令碼,以後想要獲得這個怪物資訊,找單例指令碼要去,這裡粘一下單例指令碼的Enemy程式碼
public GameObject Enemy
{
get {return _enemy;}
set {_enemy = value;}
}
C#的這種get和set方式挺簡便的,我就給試上了。然後在update的狀態機裡處理奔跑,把以前單純的奔跑改了一下,如果有Enemy,就向Enemy平滑轉身,smoothRotate是處理平滑轉身的,不清楚的可以在我前面的文章裡找到詳細程式碼。
3、英雄發起攻擊
if (isHunting) {
if (BattleScene.GetInstance().IsInAttackArea()) {
curState = en_state.en_state_attack;
BattleScene.GetInstance().SendGameMessage<int>(BattleScene.MESSAGE_TYPE.MESSAGE_ENEMY_BE_HURT,0);
isHunting = false;
}
}
case en_state.en_state_attack:
if (BattleScene.GetInstance().Enemy) {
AnimationState state = animation[curAnimClip.name];
if (state.time >= state.length-0.1f) {
int rand = Random.Range(0,3);
if (rand == 0)
curAttackState = attack_state.attack_state_0;
else if (rand == 1)
curAttackState = attack_state.attack_state_1;
else if (rand == 2)
curAttackState = attack_state.attack_state_2;
isReduceEnemyHp = true;
}
if (state.time >= state.length/2 && state.time < state.length-0.1f) {
if (isReduceEnemyHp) {
isReduceEnemyHp = false;
BattleScene.GetInstance().SendGameMessage<int>(BattleScene.MESSAGE_TYPE.MESSAGE_ENEMY_REDUCE_HP, 20);
}
}
if (curAttackState == attack_state.attack_state_0)
curAnimClip = animation["Attack"].clip;
else if (curAttackState == attack_state.attack_state_1)
curAnimClip = animation["Attack00"].clip;
else if (curAttackState == attack_state.attack_state_2)
curAnimClip = animation["Attack01"].clip;
animation.CrossFade(curAnimClip.name);
transform.Translate(Vector3.zero);
} else {
curState = en_state.en_state_idel;
curAnimClip = animation["Attack"].clip;
}
break;
程式碼有點長,前面的程式碼是在update裡隨時判斷英雄是否跑到怪物身邊,也就是是否在攻擊範圍內,如果是,置攻擊狀態,在狀態機裡處理,同時給怪物發訊息,你受到攻擊了,不要再悠閒的溜達了,趕緊還擊吧。判斷攻擊範圍程式碼如下:
public bool IsInAttackArea()
{
if (_hero && _enemy) {
CharacterController enemyController = _enemy.GetComponent<CharacterController>();
CharacterController heroController = _hero.GetComponent<CharacterController>();
float dist = Vector3.Distance(_hero.transform.position, _enemy.transform.position);
if (dist <= enemyController.radius+heroController.radius+1.0f)
return true;
}
return false;
}
注意這段程式碼是在單例腳本里的,因為要用到hero和enemy,原理就是用他們的CharacterController得到他們的半徑,如果他們的距離小於這兩個半徑之和,就說明發生碰撞了,就可以攻擊了,加1是不想他們太近而已,哈哈。
接著說狀態機裡處理攻擊的部分,先獲得攻擊動畫的屬性,如果該攻擊動畫要播完了,就隨機出下一個攻擊動畫,我這裡有三個攻擊動作用來隨機,如果攻擊動作播了一半了,大概就是刀落在怪身上了,向怪發出掉血訊息,如果怪物沒了,則重置英雄為休息狀態。這裡插播一下怪物掉血,當英雄砍到怪物身上後,發出怪掉血資訊,怪接收到掉血資訊,如上面單例指令碼的訊息傳遞部分,執行ReduceHp程式碼。
void ReduceHp(int nHp)
{
curHP -= nHp;
if (curHP <= 0) {
BattleScene.GetInstance().Enemy = null;
Destroy(gameObject);
}
}
程式碼很簡單,怪物累積減血,當血量小於等於零時,怪物死亡,刪除掉該怪物,其實如果有死亡動畫,播動畫更好,我這裡沒有,就直接刪了。
4、怪物受擊
void BeHurt()
{
enemyState = STATE_ATTACK;
}
這個很簡單,只是置怪物狀態為攻擊狀態。
5、怪物反擊
case STATE_ATTACK:
if (!BattleScene.GetInstance().IsHeroDead()) {
transform.LookAt(BattleScene.GetInstance().GetHero().transform);
animator = GetComponent <Animator>();
animator.SetBool("gocrouch", true);
animator.SetFloat("speed", 0);
if (Time.time-attackTime >= AI_THINK_TIME) {
BattleScene.GetInstance().SendGameMessage<int>(BattleScene.MESSAGE_TYPE.MESSAGE_HERO_BE_HURT, 10);
attackTime = Time.time;
}
} else {
enemyState = STATE_IDLE;
}
這裡先判斷英雄是否死了,沒有則朝向英雄,播放攻擊動作,我這裡是固定時間向英雄傳遞掉血訊息的,偷了個懶,其實應該也靠攻擊動作來判斷英雄是否掉血。如果英雄死了,重置休息狀態。
6、英雄受擊
如上面單例指令碼中的訊息傳遞所示,英雄接收掉血訊息,執行ReduceHp程式碼。
void ReduceHp(int nHp)
{
curHP -= nHp;
if (curHP <= 0) {
curHP = 0;
curState = en_state.en_state_die;
isDead = true;
}
}
case en_state.en_state_die:
animation.CrossFade("Death");
transform.Translate(Vector3.zero);
curState = en_state.en_state_none;
break;
處理掉血訊息和怪物如出一轍,就是多了個死亡動畫,不解釋了。
核心程式碼都在這裡了,後面不過就是如此反覆,誰先死就結束了,當然這裡只是非常簡單的處理方式,很不嚴密,demo而已,僅供參考。介紹到這裡,英雄掉血和怪物掉血只是數值上的體現,介面上完全看不到啊,這樣未免讓看客無法接收,我們也該為英雄和怪物做個UI了,下一篇我將講解如何用NGUI製作遊戲中的介面。