纳金网

标题: Unity3d官方工程AngryBots中的通用信号传递机制 [打印本页]

作者: wyb314    时间: 2013-1-5 23:05
标题: Unity3d官方工程AngryBots中的通用信号传递机制
        最近分析了一下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元素个数并编写相应的行为函数然后将该行为函数替换进去就行了。认真体会一下吧!我想,这个短小精悍的代码应该会在某些时刻发生一些意想不到的作用的!    好了,工程我也附上,下次见!

Built.unitypackage

0 Bytes, 下载次数: 117


作者: ku    时间: 2013-1-5 23:10
不错的文章,就是示例文件损坏了

作者: wyb314    时间: 2013-1-5 23:15
如果导不进去的话,估计你的路径出现了中文或者你直接放在桌面,试试将此包放在其他盘中的一个没有出现中文的路径中!如果还不行的话给我留个言,我好重新传一次。

作者: xiongz    时间: 2013-1-7 11:28
支持一下,顶。。。。。

作者: 所罗门封印    时间: 2013-1-7 12:21
楼主威武,学习了………………………………………………
作者: NeroDay    时间: 2013-1-10 14:12
System.Serializable这个一定要的吗
作者: look12    时间: 2013-1-10 15:54
谢谢lz的教程,System.Serializable是有什么作用的?这个好像不是类的序列化啊,类的序列化不是用于类的存储吗?

作者: wyb314    时间: 2013-1-10 16:03
只有带有了System.Serializable之后,你才能在Inspector面板上为SignalSends实例添加ReceiverItem类型子元素。不然就看不到子元素的个数,初值为0,。

作者: Sora    时间: 2013-1-15 18:16
好詳細的詳解阿 .... ! 學習學習

作者: lsermao    时间: 2013-1-18 19:20
确实不错啊,不过太高深啦
作者: 比巴卜    时间: 2013-1-21 11:19
支持一下,顶。。。。。

作者: yuji    时间: 2013-2-28 08:02
很有用的教學,感謝分享

作者: Xn10710203    时间: 2013-7-6 20:10
学习了。怎么老是要验证码啊
作者: 王者再临    时间: 2013-7-7 10:04
Xn10710203 发表于 2013-7-6 20:10
学习了。怎么老是要验证码啊

哪里来的验证码?
作者: Xn10710203    时间: 2013-7-7 17:02
王者再临 发表于 2013-7-7 10:04
哪里来的验证码?

哦不好意思,怪我没说清。回复老是要验证码。。。
作者: nanwumi    时间: 2013-7-7 17:04
好高深呀
作者: ldragon    时间: 2013-8-21 16:36
回复学习一下~~1
作者: xielei69    时间: 2013-12-9 14:52
这个不错的教程!
作者: hariboot    时间: 2013-12-9 15:13
不错,值得学习
作者: xielei69    时间: 2013-12-9 16:54
不错是不错,但还是没想清楚在以后的项目中怎么用
作者: yinzhuo    时间: 2013-12-10 12:41
感谢分享。  回复 赚点钱。。。。
作者: nianhua2008    时间: 2013-12-28 23:39
学习了,呵呵!
作者: 罗密叶    时间: 2014-3-4 15:51
没基础的人 看不懂啊··桑心




欢迎光临 纳金网 (http://course.narkii.com/club/) Powered by Discuz! X2.5