123 第1页 | 共3 页下一页
返回列表 发新帖
查看: 14215|回复: 22
打印 上一主题 下一主题

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

[复制链接]

21

主题

7

听众

651

积分

初级设计师

Rank: 3Rank: 3

纳金币
0
精华
3

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

跳转到指定楼层
楼主
发表于 2013-1-5 23:05:36 |只看该作者 |倒序浏览
        最近分析了一下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

分享到: QQ好友和群QQ好友和群 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
转播转播0 分享淘帖0 收藏收藏0 支持支持0 反对反对0
回复

使用道具 举报

ku 智囊团   

89

主题

2

听众

5万

积分

首席设计师

Rank: 8Rank: 8

纳金币
25
精华
1

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

沙发
发表于 2013-1-5 23:10:03 |只看该作者
不错的文章,就是示例文件损坏了
回复

使用道具 举报

21

主题

7

听众

651

积分

初级设计师

Rank: 3Rank: 3

纳金币
0
精华
3

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

板凳
发表于 2013-1-5 23:15:28 |只看该作者
如果导不进去的话,估计你的路径出现了中文或者你直接放在桌面,试试将此包放在其他盘中的一个没有出现中文的路径中!如果还不行的话给我留个言,我好重新传一次。
回复

使用道具 举报

907

主题

1

听众

1万

积分

资深设计师

Rank: 7Rank: 7Rank: 7

纳金币
16139
精华
6

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

地板
发表于 2013-1-7 11:28:03 |只看该作者
支持一下,顶。。。。。
回复

使用道具 举报

8

主题

4

听众

1237

积分

助理设计师

Rank: 4

纳金币
61
精华
0
5#
发表于 2013-1-7 12:21:55 |只看该作者
楼主威武,学习了………………………………………………
回复

使用道具 举报

0

主题

1

听众

75

积分

设计初学者

Rank: 1

纳金币
71
精华
0

活跃会员 灌水之王

6#
发表于 2013-1-10 14:12:51 |只看该作者
System.Serializable这个一定要的吗
回复

使用道具 举报

0

主题

5

听众

754

积分

初级设计师

Rank: 3Rank: 3

纳金币
221
精华
0

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

7#
发表于 2013-1-10 15:54:50 |只看该作者
谢谢lz的教程,System.Serializable是有什么作用的?这个好像不是类的序列化啊,类的序列化不是用于类的存储吗?
回复

使用道具 举报

21

主题

7

听众

651

积分

初级设计师

Rank: 3Rank: 3

纳金币
0
精华
3

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

8#
发表于 2013-1-10 16:03:12 |只看该作者
只有带有了System.Serializable之后,你才能在Inspector面板上为SignalSends实例添加ReceiverItem类型子元素。不然就看不到子元素的个数,初值为0,。
回复

使用道具 举报

Sora    

0

主题

1

听众

86

积分

设计初学者

Rank: 1

纳金币
4
精华
0

活跃会员 灌水之王

9#
发表于 2013-1-15 18:16:40 |只看该作者
好詳細的詳解阿 .... ! 學習學習
回复

使用道具 举报

12

主题

2

听众

657

积分

初级设计师

Rank: 3Rank: 3

纳金币
0
精华
0

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

10#
发表于 2013-1-18 19:20:43 |只看该作者
确实不错啊,不过太高深啦
回复

使用道具 举报

123 第1页 | 共3 页下一页
返回列表 发新帖
您需要登录后才可以回帖 登录 | 立即注册

手机版|纳金网 ( 闽ICP备2021016425号-2/3

GMT+8, 2024-11-28 15:57 , Processed in 0.160636 second(s), 34 queries .

Powered by Discuz!-创意设计 X2.5

© 2008-2019 Narkii Inc.

回顶部