接着,我们还要在程序中加入作为脚本调用对象的一些精灵自身方法;这里,我添加了两种方法:连续随机说话及连续随意跑动:
///
///连续随机说话
///
///
[ScriptableMember]
publicvoidRandomSay(stringcontent){
Dialogdialog=newDialog(){
Duration=5,
LocatedSpriteWidth=BodyWidth,
Top=BodyTop
};
this.Children.Add(dialog);
dialog.Completed+=(s,e)=>{
this.Children.Remove(sasDialog);
HtmlPage.Window.Invoke("RandomSay",this,newint[]{TalkContentCode});
dialog.Show(content);
}
///连续随机跑动
///
///
///
///
publicvoidRandomMoveTo(intstartX,intstartY,intendX,intendY){
Randomrandom=newRandom();
intx=random.Next(startX,endX);
inty=random.Next(startY,endY);
Pointstart=this.Coordinate;
Pointend=newPoint(x,y);
this.destination=end;
doublespendTime=Math.Sqrt(Math.Pow((end.X-start.X)/LocatedScene.GridSize,2)+Math.Pow((end.Y-start.Y)/LocatedScene.GridSize,2))*Speed*LocatedScene.GridSize;//计算总的移动花费
PointAnimationpointAnimation=newPointAnimation(){
To=end,
Duration=newDuration(TimeSpan.FromMilliseconds(spendTime))
Storyboard.SetTarget(pointAnimation,this);
Storyboard.SetTargetProperty(pointAnimation,newPropertyPath("Coordinate"));
Move();
StopMovingAnimation();
moveingAnimation=newStoryboard();
moveingAnimation.Children.Add(pointAnimation);
moveingAnimation.Completed+=(s,e)=>{
RandomMoveTo(startX,startY,endX,endY);
moveingAnimation.Begin();
最后,我们通过在场景Scene.xml配置文件中为精灵设置相应的脚本参数即可:
当场景初始化后,程序将通过如下代码自动解析该场景中每个精灵所持有的脚本:
//调用脚本
sprite.Loaded+=(s,e)=>{
stringstr=xSprite.Attribute("Scripts").Value;
if(str!=""){
string[]scripts=str.Split('_');
for(intn=0;n string[]values=scripts[n].Split(':'); string[]args=values[1].Split(','); HtmlPage.Window.Invoke(values[0],sprite,args); …… 脚本引擎的好处显而易见,通过在主程序中事先封装好对脚本的解析逻辑,配合上独立于主程序之外的xml数据存储文件加上JavaScript脚本文件即可分层实现。我们不仅可以单独的为某个精灵赋予某个脚本,也可以为其同时赋予多个脚本;更有价值的是,由于脚本文件的独立性与外置性(均可作为独立的配置文件存放于服务器上),修改时无需重新编译主程序而通过记事本即可轻松应对。当然了,有朋友或许会提出质疑,上面的随机说话脚本似乎还算凑合,但是随意移动脚本感觉牵强了些吧?主程序通过类似HtmlPage.Window.Invoke("RandomMove",sprite,newint[]{x1,y1,x2,y2});的方法调用JavaScript的RandomMove方法,而该RandomMove方法又反回调用Silverlight中的RandomMoveTo方法,这不是典型的吃饱了撑着-没事找事。其实,本节我是为了向大家演示脚本的动态性与随意组合特性而特意制作的该两个简易的脚本。实际游戏开发中,脚本会更为复杂且多变,最直接的例子就是类似《征途》中的跑商任务。假设在游戏中,某个NPC与主角对话后,将骑上代号为5的坐骑,并启动A*寻路移动到特定的目的地坐标。于是我的做法是赋予该精灵代号为0的脚本,该脚本的内容如下: //0号脚本 functionScript0(sprite,args){ sprite.Mount=5; sprite.AStarMoveTo(args[0],args[1],args[2],args[3]); 意外情况1出现了,游戏测试时项目经理觉得5号坐骑不够绚,要求换上8号坐骑,并且让该NPC处于无敌状态,我们该怎么办?很简单,用记事本修改Script0:sprite.Mount=8;sprite.State=States.Invincible;就OK了。 意外情况3出现了,NPC移动到目的地后应该立即消失,而程序员们漏掉了这点,该怎么办呢?同样很简单,首先在Sprite.cs中将精灵移动完成事件及销毁方法均标记为[ScriptableMember]: publiceventEventHandlerMovingCompleted; publicvoidDispose(){…} 接着用记事本修改Script0添加:sprite.MovingCompleted=function(sender,e){sender.Dispose();}即可。 是否简单到不行?嘿嘿,这就是脚本系统的优越性。如果不嫌麻烦,你完全可以为每个NPC精灵设定不同的脚本,而同一个脚本又可以随时随地的进行内部结构逻辑更改;不仅如此,脚本还可更广泛的作用于场景、魔法、甚至主角身上(外挂?^^HOHO),真正实现“心随我动,想怎么动就怎么动”。 到此,Silverlight脚本引擎入门就讲解完了。随便提示一下,脚本毕竟不同于游戏主程序语言,它们无需编译带来的是如同反射般性能上的问题,过度频繁的调用(解析)将会使浏览器消耗持续占用大量的CPU资源,最典型的表现就是浏览器假死;因此作为辅助,建议大家还是酌情使用,不要颠倒了主次。 下面让我们运行程序,并将主角移动的宽敞的地方(放出的精灵一旦碰到障碍物会自动停止,当然,逻辑是可以在主程序中随意修改的),测试一下成果吧: 最后,我还想补充一些关于游戏素材的设定问题,因为这会直接影响到游戏的各方面性能。 body.Clip=newRectangleGeometry(){ Rect=newRect(){ X=translateX, Y=translateY, Width=BodyWidth, Height=BodyHeight, body.RenderTransform=newTranslateTransform(){X=-translateX,Y=-translateY}; 一口气又写了这么多,总结:脚本系统作为游戏的重要辅助,对于提升游戏的灵活性及拓展性有着巨大的作用,不仅仅作为AI系统的一部分,同样它还能实现更丰富的功能,比如通过正则表达式屏蔽脏字眼以及代替XML进行数据存储等等。朋友们,Silverlight的游戏世界无比丰富,赶快加入到我们的行列中吧,未来或许会因你而改变!