假设现在我们要在视域中播放一组gif动画,时长20秒播放3遍,应该怎么办?唔,毫无疑问,你应该有个定时器,固定时间间隔检测一下是否跳转到一下帧。好的,现在我们要播放一百组这样的gif动画,在屏幕的不同地方,他们的起始时间和播放次数都不尽相同,有些快有些慢。。。相信你一定囧到了。
另举一例,假设我们要使一个物体加速前进,初始速度v,加速度a,直到命中目比地(x,y)为止。这两个例子的共同点是:我们想要达到的效果是确定的,但却不能在当前时刻立即完成,需要有一个模块(函数)来定时更新状态。我们把这种确定目标,随时间逐步更新的事件称为“动画申请”,通常用APPLY来指代和命名。毫无疑问,这个更新实体状态的函数必须是应用层定制的,以达到特定的目标。而定时触发的工作将由内核来完成。
下面我们用库函数APPLY_ShowObject的实现来示例如何完成一个动画申请函数。这个函数按照指定的半透明度变化率渐显或渐隐实体,形式如下:
BOOL FP_ShowObject(PObject* ob, BYTE* data, GameCore* m_core, DWORD time)
{
APPLYINFO_SHOWAPHPA* info=(APPLYINFO_SHOWAPHPA*)data;
if (info->alphaspeed<0
&&ob->GetAlpha()<=info->alphadest)
{
ob->SetAlpha(info->alphadest);
ob->Updata();
return TRUE;
}
else if(info->alphaspeed>0
&&ob->GetAlpha()>=info->alphadest)
{
ob->SetAlpha(info->alphadest);
ob->Updata();
return TRUE;
}
double new_alpha=ob->GetAlpha()+info->alphaspeed;
if (new_alpha<0)
new_alpha=0;
else if (new_alpha>1)
new_alpha=1.0;
ob->SetAlpha(new_alpha);
ob->Updata();
return FALSE;
}
EVENTHANDLE CClassSenior::APPLY_ShowObject( PObject* ob, //实体,不能为空
double alphaspeed, //每次变化的透明度,变化间隔取决于系统间隔
double alphadest, //当无法到达角度时,将一直旋转
DWORD nTimeSpace, //触发间隔)
SNG_MSG* CallBackMsg //回调消息)
{
if (alphadest>ob->GetAlpha()&&alphaspeed<0)
{
return 0;
}
else if (alphadest<ob->GetAlpha()&&alphaspeed>0)
{
return 0;
}
else if (alphaspeed==0)
{
return 0;
}
APPLYINFO_SHOWAPHPA temp={alphaspeed,alphadest};
APPLY* apply=GetObjectModule()->CreateApply();
apply->ObjectPtr=ob;
if (CallBackMsg)
memcpy(&apply->CallBackMsg,CallBackMsg,sizeof(SNG_MSG));
apply->fp_applyfuction=FP_ShowObject;
if (nTimeSpace<GetMinApplySpace())
nTimeSpace=GetMinApplySpace();
apply->dwSpaceCount=nTimeSpace;
memcpy(apply->param,&temp,sizeof(APPLYINFO_SHOWAPHPA));
GetCore()->PostQuest(OBJECT_MODULE,SNG_QUEST_CRAETE_APPLY,apply,NULL);
return apply->EventHandle;
}
全局函数FP_ShowObject既为状态更新函数,内核会按照APPLY结构中指定的时间间隔调用该函数。APPLY结构如下:
#define APPLY_COMMON_DATA_LENGTH 64
typedef BOOL (*FP_APPLYFUCTION)(PObject*, //实体指针
BYTE*, //数据
GameCore*, //传入GameCore
DWORD); //传入当前时间,游戏开始后的毫秒计数
typedef struct _tagAPPLY{
EVENTHANDLE EventHandle; //事件ID
_tagGroup* MyGroup; //组
PObject* ObjectPtr; //实体指针
BOOL bnLocked; //是否锁定
BOOL bnStoped; //是否已被提前结束
DWORD StartTick; //消息开始处理的时间
DWORD dwLastRunTick; //上一次触发的时间
DWORD dwSpaceCount; //间隔处理时间
BYTE param[APPLY_COMMON_DATA_LENGTH];//常用信息位
FP_APPLYFUCTION fp_applyfuction;//函数指针
SNG_MSG CallBackMsg; //事件结束时的回调消息
_tagAPPLY* NextApply;
}APPLY,*LPAPPLY;
当用户定制动画申请函数时,APPLY结构中的调用间隔,事件结束后的回调消息CallBackMsg以及函数指针是必须填写的,如需用到一些额外的参数,请将参数值以结构的形式拷贝到APPLY结构Param数组中。注意,在刷新函数FP_APPLYFUCTION中,如果返回TRUE,则该动画申请将被中止,如设置了回调消息,该消息将被异步投递。反之,则动画申请继续等待下一次被调用。
现在我们可以很简单的播放动画了,大功告成。
BITObject* bit;
bit->LoadPic("某动画");
APPLY_PlayBitObject(bit);//这是播放动画函数的库函数,具体参见ShinyNovaGui.h
下面考虑一个新的问题,假设我们要播放A动画和B动画,然后在A动画和B动画都完成后播放C动画。又或者先播放A动画后播放B动画然后播放C动画。有消息机制这个当然不难解决,我们可以为每个动画申请设计一个回调消息,只有当回调消息足够多时再继续我们的下一步流程。在代码规模较小时,这不失为一个好办法。但如果游戏中有大量这样串行或者并行的流程,那么你将为管理和处理如此之多的消息而头疼不已。为此,ShinyNova提供了一组简单的函数来实现对固定流程的约束工作:
BeginGroup();
APPLY_1();
BeginGroup();
APPLY_2();
NextGropu();
APPLY_3();
EndGroup();
NextGroup();
APPLY_4();
EndGroup();
每个Group内的动画申请将被并行执行,不同的Group将被串行执行。现在,你可以让你的游戏人物一边抽烟一边跳一曲华尔兹,然后优雅的向女士行礼了。