unity3d学习笔记(七)--利用单例脚本实现英雄与怪物的攻击与受击

本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。

http://blog.csdn.net/lzhq1982/article/details/12653945


我们的世界有了怪物,那么你怎么忍心不去虐他们一下,勇士,挥舞你的大刀,去砍他们吧。呃,有点血腥,少儿不宜。

如上一篇所说,我这里的交互全是在单例脚本中实现的。命名为BattleScene,单例脚本负责事件的分发和传递,Hero和Monster脚本负责触发和接收处理消息,设计理念是在Hero脚本中你绝对看不到Monster,同样,Monster脚本中,你也看不到Hero,他们都在单例脚本中,单例脚本中有这么一段核心的消息传递代码:

[csharp] view plain copy
  1. public void SendGameMessage<T>(MESSAGE_TYPE type, T t)  
  2.     {  
  3.         switch (type) {  
  4.         case MESSAGE_TYPE.MESSAGE_HERO_RUN_AT_TARGET:  
  5.             _hero.SendMessage("RunAtTarget", t);  
  6.             break;  
  7.         case MESSAGE_TYPE.MESSAGE_HERO_BE_HURT:  
  8.             _hero.SendMessage("ReduceHp", t);  
  9.             break;  
  10.         case MESSAGE_TYPE.MESSAGE_ENEMY_BE_HURT:  
  11.             if (_enemy)  
  12.                 _enemy.SendMessage("BeHurt");  
  13.             break;  
  14.         case MESSAGE_TYPE.MESSAGE_ENEMY_REDUCE_HP:  
  15.             if (_enemy)  
  16.                 _enemy.SendMessage("ReduceHp", t);  
  17.             break;  
  18.         }  
  19.     }  

每一条消息传递一个交互,现在看不懂没关系,下面我再细细讲解,只是这里的<T>,如果有人不清楚,可以说一下,它是C#里的泛型的概念,C++里也有类似的模板,说白了,就是可以代替你想要的类型,用处很广,这里用来传递数值,因为不知道数值会是什么类型,所以泛型就大显神威了。

言归正传,我的英雄和怪物的交互流程是这样的,点击一个怪->英雄跑过去->英雄发起攻击->怪物受击->怪物反击->英雄受击->英雄继续攻击->如此反复,直到一方死去。下面就按照这个流程看看我是怎么实现的。


1、点击一个怪

[csharp] view plain copy
  1. void OnMouseDown()  
  2.     {  
  3.         BattleScene.GetInstance().SendGameMessage<GameObject>(BattleScene.MESSAGE_TYPE.MESSAGE_HERO_RUN_AT_TARGET, gameObject);  
  4.     }  

这是怪物脚本上的响应鼠标点击的函数,就一句,向单例脚本发消息,第一个参数是消息类型,第二个参数是传递的数值,这里传的是怪物的gameobject。然后你可以对照上面的消息传递代码,向hero脚本的RunAtTarget()这个函数传递消息,意思是英雄啊,你该向那个怪跑了。


2、英雄跑过去

[csharp] view plain copy
  1. void RunAtTarget(GameObject obj)  
  2.     {  
  3.         BattleScene.GetInstance().Enemy = obj;  
  4.         curState = en_state.en_state_run;  
  5.         isHunting = true;  
  6.     }  

[csharp] view plain copy
  1. case en_state.en_state_run:  
  2.             curAnimClip = animation["Run"].clip;  
  3.             gameObject.animation.CrossFade(curAnimClip.name);  
  4.             Vector3 forward = transform.TransformDirection(Vector3.forward);  
  5.             _controller.SimpleMove(forward * runSpeed);  
  6.               
  7.             if (BattleScene.GetInstance().Enemy) {  
  8.                 smoothRotate(BattleScene.GetInstance().Enemy.transform.position);                 
  9.             } else if (runTarget != Vector3.zero) {               
  10.                 。。。  
  11.             }  
  12.             break;  

RunAtTarget的参数传递的是怪物的gameobject,但我不会将这个怪物存在hero脚本中,我把它传给了单例脚本,以后想要获得这个怪物信息,找单例脚本要去,这里粘一下单例脚本的Enemy代码
[csharp] view plain copy
  1. public GameObject Enemy  
  2.     {  
  3.         get {return _enemy;}  
  4.         set {_enemy = value;}  
  5.     }  

C#的这种get和set方式挺简便的,我就给试上了。然后在update的状态机里处理奔跑,把以前单纯的奔跑改了一下,如果有Enemy,就向Enemy平滑转身,smoothRotate是处理平滑转身的,不清楚的可以在我前面的文章里找到详细代码。


3、英雄发起攻击

[csharp] view plain copy
  1. if (isHunting) {  
  2.             if (BattleScene.GetInstance().IsInAttackArea()) {  
  3.                 curState = en_state.en_state_attack;  
  4.                 BattleScene.GetInstance().SendGameMessage<int>(BattleScene.MESSAGE_TYPE.MESSAGE_ENEMY_BE_HURT,0);  
  5.                 isHunting = false;  
  6.             }  
  7.         }  

[csharp] view plain copy
  1. case en_state.en_state_attack:  
  2.             if (BattleScene.GetInstance().Enemy) {  
  3.                 AnimationState state = animation[curAnimClip.name];  
  4.                 if (state.time >= state.length-0.1f) {  
  5.                     int rand = Random.Range(0,3);  
  6.                     if (rand == 0)  
  7.                         curAttackState = attack_state.attack_state_0;  
  8.                     else if (rand == 1)  
  9.                         curAttackState = attack_state.attack_state_1;  
  10.                     else if (rand == 2)  
  11.                         curAttackState = attack_state.attack_state_2;  
  12.                       
  13.                     isReduceEnemyHp =  true;  
  14.                 }  
  15.                 if (state.time >= state.length/2 && state.time < state.length-0.1f) {  
  16.                     if (isReduceEnemyHp) {  
  17.                         isReduceEnemyHp = false;  
  18.                         BattleScene.GetInstance().SendGameMessage<int>(BattleScene.MESSAGE_TYPE.MESSAGE_ENEMY_REDUCE_HP, 20);  
  19.                     }  
  20.                 }  
  21.                   
  22.                 if (curAttackState == attack_state.attack_state_0)  
  23.                     curAnimClip = animation["Attack"].clip;  
  24.                 else if (curAttackState == attack_state.attack_state_1)  
  25.                     curAnimClip = animation["Attack00"].clip;  
  26.                 else if (curAttackState == attack_state.attack_state_2)  
  27.                     curAnimClip = animation["Attack01"].clip;  
  28.                   
  29.                 animation.CrossFade(curAnimClip.name);                
  30.                 transform.Translate(Vector3.zero);            
  31.                   
  32.             } else {  
  33.                 curState = en_state.en_state_idel;  
  34.                 curAnimClip = animation["Attack"].clip;  
  35.             }  
  36.             break;  

代码有点长,前面的代码是在update里随时判断英雄是否跑到怪物身边,也就是是否在攻击范围内,如果是,置攻击状态,在状态机里处理,同时给怪物发消息,你受到攻击了,不要再悠闲的溜达了,赶紧还击吧。判断攻击范围代码如下:
[csharp] view plain copy
  1. public bool IsInAttackArea()  
  2.     {  
  3.         if (_hero && _enemy) {  
  4.             CharacterController enemyController = _enemy.GetComponent<CharacterController>();  
  5.             CharacterController heroController = _hero.GetComponent<CharacterController>();  
  6.             float dist = Vector3.Distance(_hero.transform.position, _enemy.transform.position);  
  7.             if (dist <= enemyController.radius+heroController.radius+1.0f)  
  8.                 return true;  
  9.         }  
  10.         return false;  
  11.     }  
注意这段代码是在单例脚本里的,因为要用到hero和enemy,原理就是用他们的CharacterController得到他们的半径,如果他们的距离小于这两个半径之和,就说明发生碰撞了,就可以攻击了,加1是不想他们太近而已,哈哈。

接着说状态机里处理攻击的部分,先获得攻击动画的属性,如果该攻击动画要播完了,就随机出下一个攻击动画,我这里有三个攻击动作用来随机,如果攻击动作播了一半了,大概就是刀落在怪身上了,向怪发出掉血消息,如果怪物没了,则重置英雄为休息状态。这里插播一下怪物掉血,当英雄砍到怪物身上后,发出怪掉血信息,怪接收到掉血信息,如上面单例脚本的消息传递部分,执行ReduceHp代码。

[csharp] view plain copy
  1. void ReduceHp(int nHp)  
  2.     {  
  3.         curHP -= nHp;  
  4.         if (curHP <= 0) {  
  5.             BattleScene.GetInstance().Enemy = null;  
  6.             Destroy(gameObject);  
  7.         }  
  8.     }  

代码很简单,怪物累积减血,当血量小于等于零时,怪物死亡,删除掉该怪物,其实如果有死亡动画,播动画更好,我这里没有,就直接删了。


4、怪物受击

[csharp] view plain copy
  1. void BeHurt()  
  2.     {  
  3.         enemyState = STATE_ATTACK;  
  4.     }  
这个很简单,只是置怪物状态为攻击状态。


5、怪物反击

[csharp] view plain copy
  1. case STATE_ATTACK:  
  2.             if (!BattleScene.GetInstance().IsHeroDead()) {  
  3.                 transform.LookAt(BattleScene.GetInstance().GetHero().transform);  
  4.                 animator = GetComponent <Animator>();  
  5.                 animator.SetBool("gocrouch"true);  
  6.                 animator.SetFloat("speed", 0);  
  7.                 if (Time.time-attackTime >= AI_THINK_TIME) {  
  8.                     BattleScene.GetInstance().SendGameMessage<int>(BattleScene.MESSAGE_TYPE.MESSAGE_HERO_BE_HURT, 10);  
  9.                     attackTime = Time.time;  
  10.                 }  
  11.             } else {  
  12.                 enemyState = STATE_IDLE;  
  13.             }  

这里先判断英雄是否死了,没有则朝向英雄,播放攻击动作,我这里是固定时间向英雄传递掉血消息的,偷了个懒,其实应该也靠攻击动作来判断英雄是否掉血。如果英雄死了,重置休息状态。


6、英雄受击

如上面单例脚本中的消息传递所示,英雄接收掉血消息,执行ReduceHp代码。

[csharp] view plain copy
  1. void ReduceHp(int nHp)  
  2.     {  
  3.         curHP -= nHp;  
  4.         if (curHP <= 0) {  
  5.             curHP = 0;  
  6.             curState = en_state.en_state_die;  
  7.             isDead = true;  
  8.         }  
  9.     }  

[csharp] view plain copy
  1. case en_state.en_state_die:  
  2.             animation.CrossFade("Death");  
  3.             transform.Translate(Vector3.zero);  
  4.             curState = en_state.en_state_none;  
  5.             break;  

处理掉血消息和怪物如出一辙,就是多了个死亡动画,不解释了。


核心代码都在这里了,后面不过就是如此反复,谁先死就结束了,当然这里只是非常简单的处理方式,很不严密,demo而已,仅供参考。介绍到这里,英雄掉血和怪物掉血只是数值上的体现,界面上完全看不到啊,这样未免让看客无法接收,我们也该为英雄和怪物做个UI了,下一篇我将讲解如何用NGUI制作游戏中的界面。



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值