1. 程式人生 > >unity3d學習筆記(七)--利用單例指令碼實現英雄與怪物的攻擊與受擊

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製作遊戲中的介面。