Unity中利用委托与监听解耦合的思路

这篇随笔是一篇记录性的随笔,记录了从http://www.sikiedu.com/my/course/304,这门课程中学到的内容,附带了一些自己的思考。

一.单例模式的应用

首先假想一种情况,现在需要有一个按钮和一个Text,当按下按钮时,Text上显示“你好”两个字。

一个最常见的方法是在按钮下挂载一个脚本BtnClick,它持有一个Text组件,它由外部的Text拖入来赋值。

在初始化时BtnClick会获取当前游戏物体下的Button组件并为其添加监听,当按下按钮时会修改Text组件中的文本内容。

具体的效果图和代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BtnClick : MonoBehaviour {

    // Use this for initialization

    public Text myText;

    void Awake ()
    {
        GetComponent<Button>().onClick.AddListener(()=>
        {
            myText.text = "你好";
        });

    }
}

BtnClick脚本

BtnClick中为Button组件添加的监听的方法是用lambda表达式写的,不懂的自行查阅资料。

这种方式有两个问题,一是耦合度过高,假如Text组件不小心被删除,点击按钮会因为找不到Text组件而报错。 二是只能作一对一的操作,无法实现复杂的交互,举个例子,假如现在有3个如上图所示的按钮,同时只有1个Text,现在要实现一个“累加”的功能,任意一个按钮被按下时,计数会加1,当累计计数到3时,让按钮显示“你好”两个字。

假如修改代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BtnClick : MonoBehaviour {

    // Use this for initialization

    public Text myText;
    private int number;

    void Awake ()
    {
        GetComponent<Button>().onClick.AddListener(()=>
        {
            number++;
            if (number >= 3)
            {
                myText.text = "你好";
            }
        });

    }
}

BtnClick脚本

此时对一个按钮按3下可以让按钮显示“你好”两个字,那如果我们复制3个相同的按钮,将Text组件拖放到3个按钮的BtnClick脚本中,是否可以满足要求?很显然,不行,因为定义的number属于类本身,3个脚本各自有属于自己的number,所以3个按钮的点击会分别叠加,没有累加效果。

那怎么办?难道要定义个全局变量来累加吗?那太蠢了,而且会很混乱。

我们先来解决问题二:

一个最常见的解决方法是为要操作的组件添加交互脚本,并在脚本中使用单例模式:

我们可以在Text组件下挂载一个脚本ShowText,脚本有一个计数器number,并提供一个Show方法,每次调用Show方法会增加计数,当计数满足条件时会获取Text组件并修改上面的内容。同时在按钮下挂载另一个脚本BtnClick,它在初始化时会获取Button组件并为其添加监听,当按下按钮时会调用ShowText脚本中的Show方法,这样可以起到累加的作用。

具体的效果图和代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ShowText : MonoBehaviour
{
    public static ShowText Instance;
    private int number;
    private void Awake()
    {
        Instance = this;
    }
    public void Show(string str)
    {
        number++;
        if (number >= 3)
        {
            GetComponent<Text>().text = str;
        }
    }
}

ShowText脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BtnClick : MonoBehaviour {

    // Use this for initialization

    void Awake ()
    {
        GetComponent<Button>().onClick.AddListener(()=>
        {
            ShowText.Instance.Show("你好");
        });

    }
}

BtnClick脚本

你可能会有疑惑,为什么要用单例模式?

要知道,我们这里之所以能实现累加,是因为3个Button的BtnClick都调用了同一个ShowText脚本中的Show方法,从而使计数能被累加。单例模式是为了保证3个BtnClick获取到的都是同一个ShowText实例对象。假如我们不用单例模式,BtnClick中点击事件的回调方法要想调用ShowText中的Show方法,就只能实例化一个对象,先不说Unity下继承自MonoBehaviour的类无法通过new来实例化,就算它可以,我们就只能这样做:

ShowText myShowText = new ShowText();

myShowText.Show();

显然,这种情况下3个按钮下BtnClick获取到的是3个不同的新创建的ShowText脚本,里面的number属性自然也无法一起累加,只能各自累加了。

虽然使用单例模式为我们解决了问题二,但仍没有解决问题一,若Text意外被删除,BtnClick会因为获取不到ShowText的单例对象而报错。

此外,即使不考虑问题一,我们考虑一种情况:假若现在不是3个按钮共同控制1个Text,而是1个按钮控制3个Text,当按下按钮时,需要让3个Text同时显示出"你好"。若用单例模式处理,在BtnClick的添加的监听方法中,需要获取每一个Text的单例对象,假若这一个按钮不只有这些Text要控制,还要控制许多其他的UI控件,那会使代码变得十分臃肿。

如何解决这两个问题?

其实这两个问题主要的症结在于当按钮被按下时,我们在按钮下挂载的脚本中直接去访问了Text组件,大大增加耦合度的同时使代码变得十分臃肿。那为了使按钮按下时不去直接访问Text组件,我们必须设计一层中间的过渡脚本,按钮按下时会到过渡脚本中访问Text组件中的方法,而且在过渡脚本中也不能直接访问Text组件下的脚本,否则一旦Text被删,一样会导致报错,但这是不可能的,过渡脚本若不访问Text组件下的脚本,怎么调用脚本中的方法呢?

我们可以退一步思考,若在Text被删的时候自动让过渡脚本知道,Text下挂载的脚本中的方法已经不存在,从而不再访问,而在Text被创建时也能让过渡脚本知道,此方法已经可以被调用了,是不是可以解决这两个问题呢?显然是的,这么做的话耦合度被大大降低,BtnClick只负责访问过渡脚本,过渡脚本中自己可以识别Text下挂载的脚本的方法是否存在,不存在就不调用,即使Text被意外删除,调用BtnClick也不会报错。

用什么机制来实现呢?用委托与监听来解决,使用委托是为了解决BtnClick关联到许多Text时需要获取很多单例带来的代码臃肿问题,委托可以看作一种函数指针,C#中可以把某些方法作为参数进行传递,参数的类型就是委托,而委托有一种很关键的特性,就是可以相加,一个委托+另一个委托,相当于同时关联了两个方法,调用这个委托时相当于同时对这两个方法进行调用。(当然,委托必须是同类型的才能叠加,比如方法的参数个数,类型要相等),我们可以利用委托的这种特性完成一个BtnClick对许多ShowText脚本下方法的同时调用。

委托的简要介绍可以参考:http://www.runoob.com/csharp/csharp-delegate.html

二.利用委托与监听解耦合

现在来描述具体实现思路:

过渡脚本中维护一个字典,字典中存放事件码(作为键)和对应的委托(作为值),事件码是一个枚举类型,由一个文件单独定义,每个事件码对应一种监听事件,比如按钮的点击,当有新的交互事件要定义时,可以手动添加新的事件码定义。字典中的值是委托类型,但委托对应的方法可以有参数,可以没参数,参数不同的委托无法相加,因此需要一个文件专门声明不同类型的委托,在过渡脚本中需要暴露3个方法,1个用来添加委托,这时就要作委托类型匹配的判断。另外一个用来移除委托,同样要类型验证,还要有一个广播方法,用来供按钮点击后调用,按钮点击时传递事件码,广播方法在字典中找到相应的委托并调用。

那什么时候进行委托的添加和移除呢?肯定是在Text初始化和销毁时,继承自MonoBehavior的类由Unity负责初始化和销毁,Unity初始化它时会调用Awake方法,销毁时会调用Destroy方法,我们在Awake时把供外部调用的方法添加到过渡脚本中的字典中,Destroy时从字典移除此委托,这就相当于让Unity来帮我们维护它们,这样,万一发生意外情况,Text被销毁了,Unity一定会调用Destroy,相应的委托就会从字典中消失,这样即使广播方法在字典中找不到对应的委托,也不至于报错,因为它并没有直接去获取并使用Text上的组件

这样,我们就需要3个文件,1个用来定义事件码,1个用来定义不同参数的委托,1个用来维护管理事件码委托的字典,并提供外部调用。

先来最简单的,事件码的文件的代码,文件名:EventType.cs,代码如下:

public enum EventType
{
    ShowText
}

EventType

当前只添加了一个事件码,需要时可手动添加

随后是定义不同参数委托的文件,文件名:CallBack.cs,代码如下:

public delegate void CallBack();
public delegate void CallBack<T>(T arg);
public delegate void CallBack<T, X>(T arg1, X arg2);
public delegate void CallBack<T, X, Y>(T arg1, X arg2, Y arg3);
public delegate void CallBack<T, X, Y, Z>(T arg1, X arg2, Y arg3, Z arg4);
public delegate void CallBack<T, X, Y, Z, W>(T arg1, X arg2, Y arg3, Z arg4, W arg5);

CallBack

这里重载了6个同名的委托,使用泛型使委托可以适应更多的情况,这些委托覆盖了参数个数从0到5的所有情况,相应地,在添加委托到字典时,就要设计多个重载的AddListener和RemoveListener函数,以便在添加和删除委托时使用相应的方法。

下面是过渡脚本,文件名:EventCenter.cs,代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EventCenter
{
    private static Dictionary<EventType, Delegate> m_EventTable = new Dictionary<EventType, Delegate>();

    private static void OnListenerAdding(EventType eventType, Delegate callBack)
    {
        if (!m_EventTable.ContainsKey(eventType))
        {
            m_EventTable.Add(eventType, null);
        }
        Delegate d = m_EventTable[eventType];
        if (d != null && d.GetType() != callBack.GetType())
        {
            throw new Exception(string.Format("尝试为事件{0}添加不同类型的委托,当前事件所对应的委托是{1},要添加的委托类型为{2}", eventType, d.GetType(), callBack.GetType()));
        }
    }
    private static void OnListenerRemoving(EventType eventType, Delegate callBack)
    {
        if (m_EventTable.ContainsKey(eventType))
        {
            Delegate d = m_EventTable[eventType];
            if (d == null)
            {
                throw new Exception(string.Format("移除监听错误:事件{0}没有对应的委托", eventType));
            }
            else if (d.GetType() != callBack.GetType())
            {
                throw new Exception(string.Format("移除监听错误:尝试为事件{0}移除不同类型的委托,当前委托类型为{1},要移除的委托类型为{2}", eventType, d.GetType(), callBack.GetType()));
            }
        }
        else
        {
            throw new Exception(string.Format("移除监听错误:没有事件码{0}", eventType));
        }
    }
    private static void OnListenerRemoved(EventType eventType)
    {
        if (m_EventTable[eventType] == null)
        {
            m_EventTable.Remove(eventType);
        }
    }
    //no parameters
    public static void AddListener(EventType eventType, CallBack callBack)
    {
        OnListenerAdding(eventType, callBack);
        m_EventTable[eventType] = (CallBack)m_EventTable[eventType] + callBack;
    }
    //Single parameters
    public static void AddListener<T>(EventType eventType, CallBack<T> callBack)
    {
        OnListenerAdding(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T>)m_EventTable[eventType] + callBack;
    }
    //two parameters
    public static void AddListener<T, X>(EventType eventType, CallBack<T, X> callBack)
    {
        OnListenerAdding(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T, X>)m_EventTable[eventType] + callBack;
    }
    //three parameters
    public static void AddListener<T, X, Y>(EventType eventType, CallBack<T, X, Y> callBack)
    {
        OnListenerAdding(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T, X, Y>)m_EventTable[eventType] + callBack;
    }
    //four parameters
    public static void AddListener<T, X, Y, Z>(EventType eventType, CallBack<T, X, Y, Z> callBack)
    {
        OnListenerAdding(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T, X, Y, Z>)m_EventTable[eventType] + callBack;
    }
    //five parameters
    public static void AddListener<T, X, Y, Z, W>(EventType eventType, CallBack<T, X, Y, Z, W> callBack)
    {
        OnListenerAdding(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T, X, Y, Z, W>)m_EventTable[eventType] + callBack;
    }

    //no parameters
    public static void RemoveListener(EventType eventType, CallBack callBack)
    {
        OnListenerRemoving(eventType, callBack);
        m_EventTable[eventType] = (CallBack)m_EventTable[eventType] - callBack;
        OnListenerRemoved(eventType);
    }
    //single parameters
    public static void RemoveListener<T>(EventType eventType, CallBack<T> callBack)
    {
        OnListenerRemoving(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T>)m_EventTable[eventType] - callBack;
        OnListenerRemoved(eventType);
    }
    //two parameters
    public static void RemoveListener<T, X>(EventType eventType, CallBack<T, X> callBack)
    {
        OnListenerRemoving(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T, X>)m_EventTable[eventType] - callBack;
        OnListenerRemoved(eventType);
    }
    //three parameters
    public static void RemoveListener<T, X, Y>(EventType eventType, CallBack<T, X, Y> callBack)
    {
        OnListenerRemoving(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T, X, Y>)m_EventTable[eventType] - callBack;
        OnListenerRemoved(eventType);
    }
    //four parameters
    public static void RemoveListener<T, X, Y, Z>(EventType eventType, CallBack<T, X, Y, Z> callBack)
    {
        OnListenerRemoving(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T, X, Y, Z>)m_EventTable[eventType] - callBack;
        OnListenerRemoved(eventType);
    }
    //five parameters
    public static void RemoveListener<T, X, Y, Z, W>(EventType eventType, CallBack<T, X, Y, Z, W> callBack)
    {
        OnListenerRemoving(eventType, callBack);
        m_EventTable[eventType] = (CallBack<T, X, Y, Z, W>)m_EventTable[eventType] - callBack;
        OnListenerRemoved(eventType);
    }

    //no parameters
    public static void Broadcast(EventType eventType)
    {
        Delegate d;
        if (m_EventTable.TryGetValue(eventType, out d))
        {
            CallBack callBack = d as CallBack;
            if (callBack != null)
            {
                callBack();
            }
            else
            {
                throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
            }
        }
    }
    //single parameters
    public static void Broadcast<T>(EventType eventType, T arg)
    {
        Delegate d;
        if (m_EventTable.TryGetValue(eventType, out d))
        {
            CallBack<T> callBack = d as CallBack<T>;
            if (callBack != null)
            {
                callBack(arg);
            }
            else
            {
                throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
            }
        }
    }
    //two parameters
    public static void Broadcast<T, X>(EventType eventType, T arg1, X arg2)
    {
        Delegate d;
        if (m_EventTable.TryGetValue(eventType, out d))
        {
            CallBack<T, X> callBack = d as CallBack<T, X>;
            if (callBack != null)
            {
                callBack(arg1, arg2);
            }
            else
            {
                throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
            }
        }
    }
    //three parameters
    public static void Broadcast<T, X, Y>(EventType eventType, T arg1, X arg2, Y arg3)
    {
        Delegate d;
        if (m_EventTable.TryGetValue(eventType, out d))
        {
            CallBack<T, X, Y> callBack = d as CallBack<T, X, Y>;
            if (callBack != null)
            {
                callBack(arg1, arg2, arg3);
            }
            else
            {
                throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
            }
        }
    }
    //four parameters
    public static void Broadcast<T, X, Y, Z>(EventType eventType, T arg1, X arg2, Y arg3, Z arg4)
    {
        Delegate d;
        if (m_EventTable.TryGetValue(eventType, out d))
        {
            CallBack<T, X, Y, Z> callBack = d as CallBack<T, X, Y, Z>;
            if (callBack != null)
            {
                callBack(arg1, arg2, arg3, arg4);
            }
            else
            {
                throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
            }
        }
    }
    //five parameters
    public static void Broadcast<T, X, Y, Z, W>(EventType eventType, T arg1, X arg2, Y arg3, Z arg4, W arg5)
    {
        Delegate d;
        if (m_EventTable.TryGetValue(eventType, out d))
        {
            CallBack<T, X, Y, Z, W> callBack = d as CallBack<T, X, Y, Z, W>;
            if (callBack != null)
            {
                callBack(arg1, arg2, arg3, arg4, arg5);
            }
            else
            {
                throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
            }
        }
    }
}

EventCenter

这里有很多要注意的点:

1.先看AddListener系列的方法,6个方法分别对应传入6种不同参数个数的委托,之所以每个里面要分两步完成,是为了进行代码精简,OnListenerAdding将6种AddListener的相同部分抽取了出来,注意为了实现抽取使用了多态,用Delegate容纳不同CallBack参数。

简单讲述下AddListener的逻辑:

1.先判断字典中有无对应事件码,没有就添加新的,新的事件码对应的的委托方法设为null

2.取出事件码对应的委托,如果这个委托不是null,且它的类型和传入的CallBack类型不同,则抛出异常。

3.经历了以上两步,显然此时事件码对应的委托要不就是null,要不就是和传入的CallBack类型相同,此时根据AddListener的不同将事件码对应的委托转换  成相同的类型并进行相加,重新赋回到字典中的对应项。

这里有一点很关键:仔细想,为什么我们要把AddListener分成那么多类型,传入不同的CallBack,能不能利用多态,第二个参数能不能直接传入Delegate?

答案是不行,因为上面的第2步会将字典中的相应Delegate与传入的CallBack进行类型比较,在同类型的情况下,第3步会把事件码对应的委托转换成传入的CallBack的类型并加回到字典中,假如传入的就是Delegate,那在第3步根本不存在类型转换,那存入字典的就一定是Delegate类型,那就根本没法在下一个第2步找出类型不同的情况。

同理,叙述一下RemoveListener的逻辑:

1.先判断字典中有无对应事件码,没有就直接抛出异常。

2.有的话取出事件码对应的委托,如果这个委托为null,抛出异常。如果不为null且它的类型和传入的CallBack类型不同,也抛出异常。

3.经历了以上两步,显然此时事件码对应的委托一定和传入的CallBack类型相同,此时根据AddListener的不同将事件码对应的委托转换成相同的类型并进行相减。

4.减完后要判断事件码对应的委托是不是null,是的话移除事件码及对应的委托。

BroadCast也一样要分多种参数类型,因为有可能要传入不同数量的参数,BroadCast的逻辑:

1.用TryGet获取对应事件码的委托,若获取失败不做任何操作(这一条保证了不会报错)

2.若获取成功,将此委托强转为对应的CallBack,这一条很重要,这就是为什么BroadCast也要分多种参数类型原因,不分的话,不知道要把委托转换成哪种具体的CallBack,而以Delegate为类型,自然无法调用相应的参数。

3.判断强转的CallBack是不是null,是代表强转失败,这是因为事件码对应的委托的参数并不是此类BroadCast能处理的,此时要抛出异常。

4.若CallBack不是null,代表强转成功,直接调用方法即可。

接下来给出测试用的ShowText脚本(挂载在Text组件上)和BtnClick脚本(挂载在按钮上)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ShowText : MonoBehaviour
{
    private void Awake()
    {
        gameObject.SetActive(false);
        EventCenter.AddListener<string, string, float, int, int>(EventType.ShowText, Show);
    }
    private void OnDestroy()
    {
        EventCenter.RemoveListener<string, string, float, int, int>(EventType.ShowText, Show);
    }
    private void Show(string str,string str1,float a, int b, int c)
    {
        gameObject.SetActive(true);
        GetComponent<Text>().text = str + str1 + a + b + c;
    }
}

ShowText脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BtnClick : MonoBehaviour {

    // Use this for initialization

    private void Awake ()
    {
        GetComponent<Button>().onClick.AddListener(()=>
        {
            EventCenter.Broadcast(EventType.ShowText,"你好","呀",1.0f,1,2);
        });

    }
}

BtnClick脚本

这个没什么好说的,ShowText在Awake时把Show方法(这里用5个参数的作演示)添加到字典,在Destroy中作移除。

BtnClick在被点击时调用BroadCast并传入相应的参数。

有一点要注意:AddListener的调用要明确指明使用的泛型,<string,string,float,int,int>,若直接

EventCenter.AddListener(EventType.ShowText, Show);

会报错,这是因为不同版本的AddListener参数个数是相同的,因此调用时若不指定调用的AddListener版本,程序不知道调用哪个。RemoveListener也是同理。但是,对BroadCast的调用则不必指定版本,因为不同版本的BroadCast参数个数不同,根据传入的参数个数,程序能自动识别调用哪个版本的BroadCast。

这种利用委托和监听解耦合的思路也是有缺点的,就是调用时顺序必须匹配,若ShowText以<string,string,float,int,int>的方式添加了委托,则调用时也必须以同样的顺序传入参数,而我们事先无法得知具体的顺序是怎样的,要在BtnClick实现调用,就只能通过跳转代码,跳转到ShowText的源代码中去了解调用顺序。

另外,我自己的看法是,这种方式可能会破坏封装性,因为ShowText中的Show方法,在ShowText中是被定义为private的,而这个方法又在ShowText被初始化的时候作为委托被添加到了EventCenter中的字典里,按下按钮时根据事件码从字典中找到对应的委托并调用,相当于在ShowText类外部可以随意对类内的私有private方法进行随意调用,安全性似乎有点问题。但这不是由这种思路带来的,而是委托机制本身带来的缺陷,抛开这门课不讲,一个方法可以通过委托传递可以让人接受,但一个类中的私有方法可以通过委托被传递,从而供外部函数随意调用,就有点奇怪了,在我看来,至少也应该设计成只有类中的公有方法才能通过委托传递才对啊,C#里的这种委托机制真的不会带来安全性的隐患吗?这只是我的一点小疑惑,可能是目前对委托的理解不够深,等以后明白了再回来填坑吧。

原文地址:https://www.cnblogs.com/czw52460183/p/10494285.html

时间: 2024-10-14 00:38:30

Unity中利用委托与监听解耦合的思路的相关文章

C#中利用委托实现多线程跨线程操作

在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了保证线程安全以及提高代码的效率所做的改进,但是也给大家带来很多不便. 其实解决这个问题有两种方法:一,是通过设置System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;在你的程序初始化的时候设置了这个属性,而且在你的控件中使用的都是微软Framework类库中的控件的话,系统就不会再抛出你上面所说的

如何为JOPtionPane的showConfirmDialog对话框中的按钮设置监听

最近刚学了java图形界面编程,想独立写个记事本,过程当中想要为对话框中的按钮设置监听,但是查看了一下api也没找到相应的方法,只找到这个: Returns: an integer indicating the option selected by the user showConfirmDialog这个方法只返回一个int值来标识用户选择了哪一个按钮.找了半天也没找到设置监听的方法..... 就在这时.....灵光一闪~~~~ 很多方法在正常情况下是返回0的,非正常情况返回-1. 于是我就用如

简述Java中Http/Https请求监听方法

一.工欲善其事必先利其器 做Web开发的人总免不了与Http/Https请求打交道,很多时候我们都希望能够直观的的看到我们发送的请求参数和服务器返回的响应信息,这个时候就需要借助于某些工具啦.本文将采用Fiddler2作为分析工具,Fiddler很强大,它能记录所有客户端和服务器的http和https请求,允许你监视,设置断点,甚至修改输入输出数据,是越墙抓包之利器.关于工具的介绍可以参考下面的链接: http://www.cnblogs.com/TankXiao/archive/2012/02

【Unity编程】Unity中关于四元数的API详解

Unity中关于四元数的API详解 Quaternion类 Quaternion(四元数)用于计算Unity旋转.它们计算紧凑高效,不受万向节锁的困扰,并且可以很方便快速地进行球面插值. Unity内部使用四元数来表示所有的旋转. Quaternion是基于复数,并不容易直观地理解. 不过你几乎不需要访问或修改单个四元数参数(x,y,z,w); 大多数情况下,你只需要获取和使用现有的旋转(例如来自"Transform"),或者用四元数来构造新的旋转(例如,在两次旋转之间平滑插入). 大

关于JAVA中事件分发和监听机制实现的代码实例-绝对原创实用

http://blog.csdn.net/5iasp/article/details/37054171 文章标题:关于JAVA中事件分发和监听机制实现的代码实例 文章地址: http://blog.csdn.net/5iasp/article/details/37054171 作者: javaboy2012Email:[email protected]qq:    1046011462 一.场景假设 假设有博客系统中需要实现如下功能: 系统中用户发布文章,修改文章,删除文章时,需要一些相关的操作

QT中使用Event Filter监听按钮事件,Release后按钮不见

Accepted : 42   Submit : 122 Time Limit : 1000 MS   Memory Limit : 65536 KB 题目描述 五虎棋是流传在东北民间的一种游戏,GG小的时候,经常被表哥虐得很惨. 由于各个地区的规则可能不大相同,并且GG的回忆不一定很准,所以,如果规则和你平常玩的的有冲突,请以这里为主. 棋盘是横五条,纵五条直线,形成25个交叉点,双方轮流把棋子放到交叉点上 (由于所需各自和棋子数目不多,才12+13,GG小的时候,用的是象棋的棋盘和棋子,真的

Qt Gui中父控件监听子控件的IO事件

父对象重新定义自己继承自QObject的函数bool eventFilter(QObject* watched, QEvent* event). 子控件安装父对象的eventFilter: 例如, QTableView * itsView = new QTableView; itsView->viewport()->installEventFilter(this); Qt Gui中父控件监听子控件的IO事件

利用select/poll监听多个设备详解

如果一个应用程序去处理多个设备,例如应用程序读取网路数据,按键,串口,一般能想到的有三种方法: 方法1: 串行+阻塞的方式读取: while(1) { read(标准输入); read(网络); } 缺点:每当阻塞读取标准输入时,如果用户不进行标准输入的操作,而此时客户端给服务器发送数据,导致服务器无法读取客户端发送来的数据! 方法2: 采用多线程或者多进程机制来实现读取: 开辟多个线程,每一个线程处理一个设备,不会导致的数据的无法读取,但是系统的开销相比方法1要大! 方案3:采用linux系统

服务中电话状态的监听

public class AddressService extends Service { public static final String tag = "AddressService"; private TelephonyManager mTM; private MyPhoneStateListener mPhoneStateListener; private final WindowManager.LayoutParams mParams = new WindowManager