我不知道它有没有专门的名称,在engine里,我叫它Stub,所谓Stub是指一个对象暴露出来和外界联系的接口,它分为两种,signal和slot,熟悉Qt的朋友肯定立即就会知道这是什么东西了,事实上这个东西我也是从Qt里借鉴过来的,只是我的实现方法和它不一样罢了.一个对象在它需要的时候可以发出各种事件,称为signal,也可以通过slot接受来自外界的事件,每个signal和slot都有一个名字,使用这些对象时,可以通过这些名字来连接指定的signal和slot,使对象之间协同工作.举几个例子:
最简单的就是按钮了,当它被点击时会发出一个Click的signal:
一个延时器,它有一个名叫Start的slot,用来触发计时开始,然后当指定的时间到了以后,会发出一个TimeUp的signal:
一个角色控制器,它有一个名叫UserInput的slot,用来接收各种鼠标/键盘消息,处理后,会把它转换成对角色的各种操控命令,以signal的形式发出,比如Move,Turn等,未处理的消息,通过UnHandled发出.
玩过Crysis的朋友应该会有点眼熟吧,至少从界面上和Crysis Editor的Flow Graph有点像,事实上Crysis的Flow Graph也的确是这套系统的重点参考对象.
下面说说这个东西的实现,在目前的实现中,一个signal的本质是一个函数调用,而不是一个消息什么的,我们为每一个signal维护一个函数指针的队列,发出一个signal时,实际做的事情是去找到这些函数指针,并一一调用它们,而slot其实就是一个可以拿到函数指针的对外接口,并且与之对应需要实现一个处理函数,用来响应来自signal的调用.连接signal和slot的时候,我们根据slot名字到Target对象中找出一个函数指针,然后把它添加到Source对象的signal的函数指针队列中去.既然是函数调用,就会有参数,目前一次调用只能传递一个参数,我叫它Property,所有的Property都派生自一个基类,用来抽象出参数的new/delete/复制/比较/类型转换等一些基本操作,然后我为大多数常用的数值类型(Int,Float,Vector3D,Matrix,String等)都各写了一个Property的类,这样这些类型的数值就可以成为signal/slot的调用参数了,当然如果需要的话你也可以定义自己需要的Property.我还写了一个"百搭"的Property,它可以存储多个Property,并且它可以和其它类型Property相互转换,这样使一次调用传递多个参数成为可能.Stub的描述信息(比如名称,注释,参数类型等)是作为静态成员变量写在类里面的,而signal的函数指针队列则是每个对象独有的,必须写成成员变量.当然我还是用我的一贯丑陋的宏+模板的方式来完成这套系统.
上面是对实现的粗略的介绍,我想可能并没能讲得很清楚,不过还是那句话,知道做什么比知道怎么做要重要的多.大多数有经验的程序员应该都能写出一套这样的东西的,而且可能会写得更漂亮.
下面是几个使用的例子:
class CButton
{
public:
...
...
protected:
GStubBegin(CButton)
GSignalVoid(Clicked, "按钮被点击") //这个Signal没有参数,
GStubEnd()
...
...
void OnClick()
{
...
StubFireVoid(Clicked);//发送Clicked的signal
}
};
class CDelay
{
public:
...
...
protected:
GStubBegin(CDelay)
GSlotVoid(Start, "开始")
GSignalVoid(TimeUp, "时间到")
GStubEnd()
//Start 的处理函数
void handler_Start()
{
//开始计时
...
}
void Update()
{
...
...
if (IsTimeUp())//时间到
StubFireVoid(TimeUp);
}
...
...
};
struct PropInput:public PropertyBase
{
//鼠标,键盘消息
...
...
};
class CCharCtrl
{
public:
...
...
protected:
GStubBegin(CCharCtrl)
GSlotDefine(UserInput,PropInput,"键盘/鼠标输入消息");//这个Slot接受一个PropInput类型的参数
GSignalDefine(UnHandled,PropInput,"未处理的键盘/鼠标输入消息");//这个Signal发送一个PropInput类型的参数
GSignalVec3D(Move,"朝某方向移动");//参数为一个Vector3D,代表一个方向
GSignalVoid(StopMove,"停止移动消息");
GSignalVoid(TurnLeft,"向左转动消息");
GSignalVoid(TurnRight,"向右转动消息");
GSignalVoid(StopTurn,"停止转动消息");
GSignalVec3D(TurnTo,"旋转到指定角度");//参数为一个Vector3D,代表一个欧拉角
GStubEnd()
//UserInput 的处理函数
void handler_UserInput(PropInput *input)
{
BOOL bHandled=FALSE;
//根据输入发送各种控制命令(Move,StopMove,TurnLeft,TurnRight,StopTurn,TurnTo)
...
...
if (!bHandled)
StubFire(PropInput,input);
...
}
...
...
};
{
public:
...
...
protected:
GStubBegin(CButton)
GSignalVoid(Clicked, "按钮被点击") //这个Signal没有参数,
GStubEnd()
...
...
void OnClick()
{
...
StubFireVoid(Clicked);//发送Clicked的signal
}
};
class CDelay
{
public:
...
...
protected:
GStubBegin(CDelay)
GSlotVoid(Start, "开始")
GSignalVoid(TimeUp, "时间到")
GStubEnd()
//Start 的处理函数
void handler_Start()
{
//开始计时
...
}
void Update()
{
...
...
if (IsTimeUp())//时间到
StubFireVoid(TimeUp);
}
...
...
};
struct PropInput:public PropertyBase
{
//鼠标,键盘消息
...
...
};
class CCharCtrl
{
public:
...
...
protected:
GStubBegin(CCharCtrl)
GSlotDefine(UserInput,PropInput,"键盘/鼠标输入消息");//这个Slot接受一个PropInput类型的参数
GSignalDefine(UnHandled,PropInput,"未处理的键盘/鼠标输入消息");//这个Signal发送一个PropInput类型的参数
GSignalVec3D(Move,"朝某方向移动");//参数为一个Vector3D,代表一个方向
GSignalVoid(StopMove,"停止移动消息");
GSignalVoid(TurnLeft,"向左转动消息");
GSignalVoid(TurnRight,"向右转动消息");
GSignalVoid(StopTurn,"停止转动消息");
GSignalVec3D(TurnTo,"旋转到指定角度");//参数为一个Vector3D,代表一个欧拉角
GStubEnd()
//UserInput 的处理函数
void handler_UserInput(PropInput *input)
{
BOOL bHandled=FALSE;
//根据输入发送各种控制命令(Move,StopMove,TurnLeft,TurnRight,StopTurn,TurnTo)
...
...
if (!bHandled)
StubFire(PropInput,input);
...
}
...
...
};