分享

Unity3d官方工程AngryBots中的通用信号传递机制

 3dC 2014-04-12
         最近分析了一下Unity自带的AngryBots中的代码,发现了一个相当强大的信号传递框架,并且代码并不复杂。它之所以有用是因为此框架维护起来非常方便,就像我之前介绍的那个状态机框架的道理是一样的。这个框架仅仅只有一个脚本,你可能对此产生不屑,不过,当你看到了它的魔术般的效果时,你一定会对你之前的藐视而感到惭愧的。好了,话多无益,实践才是硬道理。         首先,我们来思考一个问题:假如在某一时刻,我们需要做一连串动作,且执行该行为的接收者也各不相同,甚至是同一个接收者在此条件下得执行不同脚本中的某些函数,此时我们的代码该怎样写?例如:当我按下攻击键,我自身必须播放射击的动画,角色自身是此攻击信号的第一个接收者;然后角色的枪口要克隆出一个子弹,并且产生火花,还要播放子弹出膛的音频,此时枪口可以设计成第二个攻击信号的接收者,且要连续执行三个不同的行为,如果主角此时拿的是火箭筒,那么此时的接收者主角还要向后挪动一小段距离,也就是说此接收者还要多执行一个动作。通常情况下,我们会事先在枪口的某一个位置安放一个子弹出膛点,然后绑定一个音频源,再挂接相应的脚本,当我们按下开枪按键时,会获取有关的脚本,然后调用里面的相应的函数,执行该动作。可是我们会发现,这样做的话,代码会变得异常混乱,并不利于我们今后对代码的维护,所以我们得尽量想其他的办法。这就是我今天介绍这个框架的原因。
    我将AngryBots里面的那个代码给翻译成了C#,途中出了一些挫折,原因出在类的序列化,先看代码:
/******************************************************************************/

using UnityEngine;

using System.Collections;
[System.Serializable]

public class ReceiverItem
{  
    public GameObject receiver;//信号接收者    
    public string action = "OnSignal";//默认执行的函数名,可以在Inspector面板上面修改
    public float delay;//执行在执行该行为之前的等待时间,默认是不必等待的
    public IEnumerator SendWithDelay(MonoBehaviour sender)
    { 
       yield return new WaitForSeconds(delay);//等待delay秒
        if (receiver)//如果接收者存在

            receiver.SendMessage(action, SendMessageOptions.DontRequireReceiver);//就执行该接收者附着的脚本中的名为action的函数

        else
            Debug.LogWarning("No receiver of signal "" + action + "" on object " + sender.name + " (" + sender.GetType().Name + ")", sender);
   }
}

[System.Serializable]


public class SignalSends

 {
    public ReceiverItem[] receives; 
    public void SendSignal(MonoBehaviour sender)
    {
        for (int i = 0; i < receives.Length; i++) 
       {
            sender.StartCoroutine(receives.SendWithDelay(sender));执行协同函数
        }
    }
}
/******************************************************************************/


这就是此框架的核心,你也许会说,这么短的代码,能起多大作用?好吧,我们还是以一个例子来说明这一切吧!为了节省时间,我还是以我的上一篇帖子中的工程作为基础,修改一下里面的几个脚本。第一步,往工程里面添加一个Audio文件夹,并导入两个音频片(我自己找的两个音频片),如下:

第二步,修改代码及Inspector面板中的信号变量的拖拽

SpawnBullet.cs修改如下:
using UnityEngine;

using System.Collections;public class SpawnBullet : MonoBehaviour
 { 

   public GameObject bullet;   
 //public Transform Spawnpoint;   
 public SignalSends spawnSignal;//植入spawnSignal实例 
 


  void OnGUI() 

  {

        if(GUILayout.Button("SpawnBullet"))       
 

/*********************************************************************************/  
       //修改部分  
            spawnSignal.SendSignal(this);//发送子弹出膛信号
            //Instantiate(bullet,Spawnpoint.position,Quaternion.identity);
 /*********************************************************************************/
            }
    }
}


SpawnBulletPoint.cs的修改:
using UnityEngine;
using System.Collections;

public class SpawnBulletPoint : MonoBehaviour 
{

    public float radius = 2f;
/*********************************************************************************/
   //修改部分
    public GameObject bullet;//后来得将子弹预设拖拽上去
/*********************************************************************************/
   void OnDrawGizmos()

     { 
       Gizmos.color = Color.green;
        Gizmos.DrawSphere(transform.position, radius);
    }


/*********************************************************************************/

    //修改部分    

void InstantiateBullet()

  {

        Instantiate(bullet, transform.position, Quaternion.identity); 
   }
    void PlayAudio()
     { 

       if(audio)
        { 
              audio.Play(); 
       } 
   }

/*********************************************************************************/  

  }

下面我们开始最关键的一步:选中MainCamera,然后在Inspector面板上将连个信号接收实例定义为一个,且里面的接收者元素有两个,如下:
我们看SpawnSignal,此时该变量下元素类型为Receives的实例有两个,但都为SpawnPoint,只是Action不同,留心观察一下,这两个Action是可以在Inspector面板中临时填写的,当前填写的名字正好是SpawnBulletPoint .cs脚本中的两个新添加的函数名。这就是这个信号发射系统的关键所在。接下来我们修改BulletController.cs脚本:
using UnityEngine;using System.Collections;
public class BulletController : MonoBehaviour {
    public float speed = 1f;    public float distance = 0.1f;/*********************************************************************************/    //修改部分    public SignalSends explosionSignal;    public AudioClip explosion;/*********************************************************************************/   
    private LayerMask mask;

// Use this for initializationvoid Start () {        mask = 1 << LayerMask.NameToLayer("wall1");}// Update is called once per framevoid Update () {        transform.Translate(transform.forward * speed * Time.deltaTime );        RaycastHit hit;        if(Physics.Raycast(transform.position,transform.forward,out hit,distance,mask))        {/*********************************************************************************/    //修改部分            explosionSignal.SendSignal(this);/*********************************************************************************/                //Debug.Log("The bullet hit the target");            //Destroy(this.gameObject);        }}
/*********************************************************************************/    //修改部分    void***cuteExplosion()     {        if(explosion)        {            AudioSource.PlayClipAtPoint(explosion,transform.position);        }        Destroy(this.gameObject);    }/*********************************************************************************/    }修改的目的是:当子弹撞击第二块挡板时播放爆炸的音频。但用的是信号发射框架,具体修改部分我还是将工程上传上去大家下载下来自行研究吧!运行之后,功能全都实现了。我们再回头看看这个信号发射框架中的后一个类的代码:[System.Serializable]public class SignalSends {    public ReceiverItem[] receives;    public void SendSignal(MonoBehaviour sender)/*    这个函数就是我们之前发射信号用的,传递的参数就是脚本本身,即this。
*/    {        for (int i = 0; i < receives.Length; i++)        {/*    遍历各个接收者,然后执行接收者中绑定的一个或多个脚本中的某个函数
*/            sender.StartCoroutine(receives.SendWithDelay(sender));执行协同函数        }    }}整个流程丝毫不拖泥带水,我们理论上可以在Inspector面板中的SignalSends实例中定义无数个接收者,并执行相应的名为Action函数。我们只需调用SendSignal(this)函数,就不用在当前脚本中编写纷繁复杂的行为代码了,只需在特定的脚本中编写Action函数就行,然后再Inspector面板中将此Action函数名添加进去就行了。如此一来,我们就省去了获取GameObject实例,获取脚本实例,然后才能调用特定函数且有可能传递一些参数的这些步骤了,我们就能更方便的编写行为代码,且当行为增加时,我们只需增加SignalSends实例中的ReceiverItem元素个数并编写相应的行为函数然后将该行为函数替换进去就行了。认真体会一下吧!我想,这个短小精悍的代码应该会在某些时刻发生一些意想不到的作用的!    好了,工程我也附上,下次见!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多